From 71a81f7cd6bc8aec7f52d8c26b1b1aee937859e4 Mon Sep 17 00:00:00 2001
From: Ashokaditya
Date: Mon, 14 Jun 2021 15:39:11 +0200
Subject: [PATCH 01/91] [Security Solution][Endpoint] Actions Log API (#101032)
* WIP
add tabs for endpoint details
* fetch activity log for endpoint
this is work in progress with dummy data
* refactor to hold host details and activity log within endpointDetails
* api for fetching actions log
* add a selector for getting selected agent id
* use the new api to show actions log
* review changes
* move util function to common/utils
in order to use it in endpoint_hosts as well as in trusted _apps
review suggestion
* use util function to get API path
review suggestion
* sync url params with details active tab
review suggestion
* fix types due to merge commit
refs 3722552f739f74d3c457e8ed6cf80444aa6dfd06
* use AsyncResourseState type
review suggestions
* sort entries chronologically with recent at the top
* adjust icon sizes within entries to match mocks
* remove endpoint list paging stuff (not for now)
* fix import after sync with master
* make the search bar work (sort of)
this needs to be fleshed out in a later PR
* add tests to middleware for now
* use snake case for naming routes
review changes
* rename and use own relative time function
review change
* use euiTheme tokens
review change
* add a comment
review changes
* log errors to kibana log and unwind stack
review changes
* search on two indices
* fix types
* use modified data
* distinguish between responses and actions and respective states in UI
* use indices explicitly and tune the query
* fix types after sync with master
* fix lint
* do better types
review suggestion
* add paging to API call
* add paging info to redux store for activityLog
* decouple paging action from other API requests
* use a button for now to fetch more data
* add index to fleet indices
else we get a type check error about the constant not being exported correctly
from `x-pack/plugins/fleet/common/constants/agent`
* add tests for audit log API
* do semantic paging from first request
* fix ts error
review changes
* add document id and total to API
review suggestions
* update test
* update frontend to consume the modified api correctly
* update mock
* rename action
review changes
* wrap mock into function to create anew on each test
review changes
* wrap with schema.maybe and increase page size
review changes
* ignore 404
review changes
* use i18n
review changes
* abstract logEntry component logic
review changes
* move handler logic to a service
review changes
* update response object
review changes
* fix paging to use 50 as initial fetch size
* fix translations and move custom hook to component file
review changes
* add return type
review changes
* update default value for page_size
review changes
* remove default values
review changes
https://github.com/elastic/kibana/tree/master/packages/kbn-config-schema#schemamaybe
https://github.com/elastic/kibana/tree/master/packages/kbn-config-schema#default-values
* fix mock data
refs 1f9ae7019498c616455f1109a524741e212106ca
* add selectors for data
review changes
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../plugins/fleet/common/constants/agent.ts | 1 +
.../plugins/fleet/common/constants/index.ts | 1 +
.../common/endpoint/schema/actions.ts | 11 +-
.../common/endpoint/types/actions.ts | 26 +++
.../pages/endpoint_hosts/store/action.ts | 10 +-
.../pages/endpoint_hosts/store/builders.ts | 6 +-
.../pages/endpoint_hosts/store/index.test.ts | 4 +-
.../endpoint_hosts/store/middleware.test.ts | 45 +++-
.../pages/endpoint_hosts/store/middleware.ts | 70 +++++-
.../pages/endpoint_hosts/store/reducer.ts | 30 ++-
.../pages/endpoint_hosts/store/selectors.ts | 28 ++-
.../management/pages/endpoint_hosts/types.ts | 8 +-
.../view/details/components/log_entry.tsx | 199 ++++++++++++++----
.../components/log_entry_timeline_icon.tsx | 42 ++++
.../view/details/endpoint_activity_log.tsx | 49 ++++-
.../view/details/endpoints.stories.tsx | 142 ++++++++-----
.../endpoint_hosts/view/details/index.tsx | 12 +-
.../pages/endpoint_hosts/view/translations.ts | 50 ++++-
.../endpoint/routes/actions/audit_log.test.ts | 186 ++++++++++++++++
.../routes/actions/audit_log_handler.ts | 49 ++---
.../server/endpoint/routes/actions/mocks.ts | 23 ++
.../server/endpoint/routes/actions/service.ts | 110 ++++++++++
22 files changed, 919 insertions(+), 183 deletions(-)
create mode 100644 x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry_timeline_icon.tsx
create mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts
create mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/actions/service.ts
diff --git a/x-pack/plugins/fleet/common/constants/agent.ts b/x-pack/plugins/fleet/common/constants/agent.ts
index 6d85f658f2240..e38b7a6b5832b 100644
--- a/x-pack/plugins/fleet/common/constants/agent.ts
+++ b/x-pack/plugins/fleet/common/constants/agent.ts
@@ -25,3 +25,4 @@ export const AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL = 5;
export const AGENTS_INDEX = '.fleet-agents';
export const AGENT_ACTIONS_INDEX = '.fleet-actions';
+export const AGENT_ACTIONS_RESULTS_INDEX = '.fleet-actions-results';
diff --git a/x-pack/plugins/fleet/common/constants/index.ts b/x-pack/plugins/fleet/common/constants/index.ts
index e3001542e3e6f..ef8cb63f132b4 100644
--- a/x-pack/plugins/fleet/common/constants/index.ts
+++ b/x-pack/plugins/fleet/common/constants/index.ts
@@ -31,6 +31,7 @@ export const FLEET_SERVER_SERVERS_INDEX = '.fleet-servers';
export const FLEET_SERVER_INDICES = [
'.fleet-actions',
+ '.fleet-actions-results',
'.fleet-agents',
FLEET_SERVER_ARTIFACTS_INDEX,
'.fleet-enrollment-api-keys',
diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts
index 09776b57ed8ea..f58dd1f3370d4 100644
--- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { schema } from '@kbn/config-schema';
+import { schema, TypeOf } from '@kbn/config-schema';
export const HostIsolationRequestSchema = {
body: schema.object({
@@ -22,13 +22,18 @@ export const HostIsolationRequestSchema = {
};
export const EndpointActionLogRequestSchema = {
- // TODO improve when using pagination with query params
- query: schema.object({}),
+ query: schema.object({
+ page: schema.number({ defaultValue: 1, min: 1 }),
+ page_size: schema.number({ defaultValue: 10, min: 1, max: 100 }),
+ }),
params: schema.object({
agent_id: schema.string(),
}),
};
+export type EndpointActionLogRequestParams = TypeOf;
+export type EndpointActionLogRequestQuery = TypeOf;
+
export const ActionStatusRequestSchema = {
query: schema.object({
agent_ids: schema.oneOf([
diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts
index 937025f35dadc..99753242e7627 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts
@@ -38,6 +38,32 @@ export interface EndpointActionResponse {
action_data: EndpointActionData;
}
+export interface ActivityLogAction {
+ type: 'action';
+ item: {
+ // document _id
+ id: string;
+ // document _source
+ data: EndpointAction;
+ };
+}
+export interface ActivityLogActionResponse {
+ type: 'response';
+ item: {
+ // document id
+ id: string;
+ // document _source
+ data: EndpointActionResponse;
+ };
+}
+export type ActivityLogEntry = ActivityLogAction | ActivityLogActionResponse;
+export interface ActivityLog {
+ total: number;
+ page: number;
+ pageSize: number;
+ data: ActivityLogEntry[];
+}
+
export type HostIsolationRequestBody = TypeOf;
export interface HostIsolationResponse {
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts
index 178f27caa1085..a8885b904d9c8 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts
@@ -148,8 +148,15 @@ export type EndpointIsolationRequestStateChange = Action<'endpointIsolationReque
payload: EndpointState['isolationRequestState'];
};
+export interface AppRequestedEndpointActivityLog {
+ type: 'appRequestedEndpointActivityLog';
+ payload: {
+ page: number;
+ pageSize: number;
+ };
+}
export type EndpointDetailsActivityLogChanged = Action<'endpointDetailsActivityLogChanged'> & {
- payload: EndpointState['endpointDetails']['activityLog'];
+ payload: EndpointState['endpointDetails']['activityLog']['logData'];
};
export type EndpointAction =
@@ -157,6 +164,7 @@ export type EndpointAction =
| ServerFailedToReturnEndpointList
| ServerReturnedEndpointDetails
| ServerFailedToReturnEndpointDetails
+ | AppRequestedEndpointActivityLog
| EndpointDetailsActivityLogChanged
| ServerReturnedEndpointPolicyResponse
| ServerFailedToReturnEndpointPolicyResponse
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts
index d5416d9f8ec96..273b4279851fd 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts
@@ -19,7 +19,11 @@ export const initialEndpointPageState = (): Immutable => {
loading: false,
error: undefined,
endpointDetails: {
- activityLog: createUninitialisedResourceState(),
+ activityLog: {
+ page: 1,
+ pageSize: 50,
+ logData: createUninitialisedResourceState(),
+ },
hostDetails: {
details: undefined,
detailsLoading: false,
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts
index 5be67a3581c9e..455c6538bcdf2 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts
@@ -43,7 +43,9 @@ describe('EndpointList store concerns', () => {
error: undefined,
endpointDetails: {
activityLog: {
- type: 'UninitialisedResourceState',
+ page: 1,
+ pageSize: 50,
+ logData: { type: 'UninitialisedResourceState' },
},
hostDetails: {
details: undefined,
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
index 98ef5a341ac9e..130f8a56fd026 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
@@ -18,7 +18,7 @@ import {
Immutable,
HostResultList,
HostIsolationResponse,
- EndpointAction,
+ ActivityLog,
ISOLATION_ACTIONS,
} from '../../../../../common/endpoint/types';
import { AppAction } from '../../../../common/store/actions';
@@ -233,16 +233,40 @@ describe('endpoint list middleware', () => {
},
});
};
+
const fleetActionGenerator = new FleetActionGenerator(Math.random().toString());
- const activityLog = [
- fleetActionGenerator.generate({
- agents: [endpointList.hosts[0].metadata.agent.id],
- }),
- ];
+ const actionData = fleetActionGenerator.generate({
+ agents: [endpointList.hosts[0].metadata.agent.id],
+ });
+ const responseData = fleetActionGenerator.generateResponse({
+ agent_id: endpointList.hosts[0].metadata.agent.id,
+ });
+ const getMockEndpointActivityLog = () =>
+ ({
+ total: 2,
+ page: 1,
+ pageSize: 50,
+ data: [
+ {
+ type: 'response',
+ item: {
+ id: '',
+ data: responseData,
+ },
+ },
+ {
+ type: 'action',
+ item: {
+ id: '',
+ data: actionData,
+ },
+ },
+ ],
+ } as ActivityLog);
const dispatchGetActivityLog = () => {
dispatch({
type: 'endpointDetailsActivityLogChanged',
- payload: createLoadedResourceState(activityLog),
+ payload: createLoadedResourceState(getMockEndpointActivityLog()),
});
};
@@ -270,11 +294,10 @@ describe('endpoint list middleware', () => {
dispatchGetActivityLog();
const loadedDispatchedResponse = await loadedDispatched;
- const activityLogData = (loadedDispatchedResponse.payload as LoadedResourceState<
- EndpointAction[]
- >).data;
+ const activityLogData = (loadedDispatchedResponse.payload as LoadedResourceState)
+ .data;
- expect(activityLogData).toEqual(activityLog);
+ expect(activityLogData).toEqual(getMockEndpointActivityLog());
});
});
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
index a1da3c072293e..aa0afe5ec980a 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
@@ -8,7 +8,7 @@
import { Dispatch } from 'redux';
import { CoreStart, HttpStart } from 'kibana/public';
import {
- EndpointAction,
+ ActivityLog,
HostInfo,
HostIsolationRequestBody,
HostIsolationResponse,
@@ -32,6 +32,8 @@ import {
getIsIsolationRequestPending,
getCurrentIsolationRequestState,
getActivityLogData,
+ getActivityLogDataPaging,
+ getLastLoadedActivityLogData,
} from './selectors';
import { EndpointState, PolicyIds } from '../types';
import {
@@ -336,21 +338,25 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory(getActivityLogData(getState())),
+ payload: createLoadingResourceState(getActivityLogData(getState())),
});
try {
- const activityLog = await coreStart.http.get(
- resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, { agent_id: selectedAgent(getState()) })
- );
+ const { page, pageSize } = getActivityLogDataPaging(getState());
+ const route = resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, {
+ agent_id: selectedAgent(getState()),
+ });
+ const activityLog = await coreStart.http.get(route, {
+ query: { page, page_size: pageSize },
+ });
dispatch({
type: 'endpointDetailsActivityLogChanged',
- payload: createLoadedResourceState(activityLog),
+ payload: createLoadedResourceState(activityLog),
});
} catch (error) {
dispatch({
type: 'endpointDetailsActivityLogChanged',
- payload: createFailedResourceState(error.body ?? error),
+ payload: createFailedResourceState(error.body ?? error),
});
}
@@ -371,6 +377,56 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory(getActivityLogData(getState())),
+ });
+
+ try {
+ const { page, pageSize } = getActivityLogDataPaging(getState());
+ const route = resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, {
+ agent_id: selectedAgent(getState()),
+ });
+ const activityLog = await coreStart.http.get(route, {
+ query: { page, page_size: pageSize },
+ });
+
+ const lastLoadedLogData = getLastLoadedActivityLogData(getState());
+ if (lastLoadedLogData !== undefined) {
+ const updatedLogDataItems = [
+ ...new Set([...lastLoadedLogData.data, ...activityLog.data]),
+ ] as ActivityLog['data'];
+
+ const updatedLogData = {
+ total: activityLog.total,
+ page: activityLog.page,
+ pageSize: activityLog.pageSize,
+ data: updatedLogDataItems,
+ };
+ dispatch({
+ type: 'endpointDetailsActivityLogChanged',
+ payload: createLoadedResourceState(updatedLogData),
+ });
+ // TODO dispatch 'noNewLogData' if !activityLog.length
+ // resets paging to previous state
+ } else {
+ dispatch({
+ type: 'endpointDetailsActivityLogChanged',
+ payload: createLoadedResourceState(activityLog),
+ });
+ }
+ } catch (error) {
+ dispatch({
+ type: 'endpointDetailsActivityLogChanged',
+ payload: createFailedResourceState(error.body ?? error),
+ });
+ }
+ }
+
// Isolate Host
if (action.type === 'endpointIsolationRequest') {
return handleIsolateEndpointHost(store, action);
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
index 19235b792b270..b580664512eb6 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
@@ -33,7 +33,10 @@ const handleEndpointDetailsActivityLogChanged: CaseReducer
+): Immutable> => {
+ return {
+ page: state.endpointDetails.activityLog.page,
+ pageSize: state.endpointDetails.activityLog.pageSize,
+ };
+};
+
export const getActivityLogData = (
state: Immutable
-): Immutable => state.endpointDetails.activityLog;
+): Immutable =>
+ state.endpointDetails.activityLog.logData;
+
+export const getLastLoadedActivityLogData: (
+ state: Immutable
+) => Immutable | undefined = createSelector(getActivityLogData, (activityLog) => {
+ return getLastLoadedResourceState(activityLog)?.data;
+});
export const getActivityLogRequestLoading: (
state: Immutable
@@ -375,6 +394,13 @@ export const getActivityLogRequestLoaded: (
isLoadedResourceState(activityLog)
);
+export const getActivityLogIterableData: (
+ state: Immutable
+) => Immutable = createSelector(getActivityLogData, (activityLog) => {
+ const emptyArray: ActivityLog['data'] = [];
+ return isLoadedResourceState(activityLog) ? activityLog.data.data : emptyArray;
+});
+
export const getActivityLogError: (
state: Immutable
) => ServerApiError | undefined = createSelector(getActivityLogData, (activityLog) => {
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
index 53ddfaee7aa05..eed2182d41809 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
@@ -6,6 +6,7 @@
*/
import {
+ ActivityLog,
HostInfo,
Immutable,
HostMetadata,
@@ -14,7 +15,6 @@ import {
PolicyData,
MetadataQueryStrategyVersions,
HostStatus,
- EndpointAction,
HostIsolationResponse,
} from '../../../../common/endpoint/types';
import { ServerApiError } from '../../../common/types';
@@ -36,7 +36,11 @@ export interface EndpointState {
/** api error from retrieving host list */
error?: ServerApiError;
endpointDetails: {
- activityLog: AsyncResourceState;
+ activityLog: {
+ page: number;
+ pageSize: number;
+ logData: AsyncResourceState;
+ };
hostDetails: {
/** details data for a specific host */
details?: Immutable;
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx
index de6d2ecf36ecc..f8574da7f0a03 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx
@@ -5,53 +5,162 @@
* 2.0.
*/
-import React, { memo } from 'react';
+import React, { memo, useMemo } from 'react';
+import styled from 'styled-components';
-import { EuiAvatar, EuiComment, EuiText } from '@elastic/eui';
-import { Immutable, EndpointAction } from '../../../../../../../common/endpoint/types';
+import { EuiComment, EuiText, EuiAvatarProps, EuiCommentProps, IconType } from '@elastic/eui';
+import { Immutable, ActivityLogEntry } from '../../../../../../../common/endpoint/types';
import { FormattedDateAndTime } from '../../../../../../common/components/endpoint/formatted_date_time';
-import { useEuiTheme } from '../../../../../../common/lib/theme/use_eui_theme';
-
-export const LogEntry = memo(
- ({ endpointAction }: { endpointAction: Immutable }) => {
- const euiTheme = useEuiTheme();
- const isIsolated = endpointAction?.data.command === 'isolate';
-
- // do this better when we can distinguish between endpoint events vs user events
- const iconType = endpointAction.user_id === 'sys' ? 'dot' : isIsolated ? 'lock' : 'lockOpen';
- const commentType = endpointAction.user_id === 'sys' ? 'update' : 'regular';
- const timelineIcon = (
-
- );
- const event = `${isIsolated ? 'isolated' : 'unisolated'} host`;
- const hasComment = !!endpointAction.data.comment;
-
- return (
-
- {hasComment ? (
-
- {endpointAction.data.comment}
-
- ) : undefined}
-
- );
+import { LogEntryTimelineIcon } from './log_entry_timeline_icon';
+
+import * as i18 from '../../translations';
+
+const useLogEntryUIProps = (
+ logEntry: Immutable
+): {
+ actionEventTitle: string;
+ avatarSize: EuiAvatarProps['size'];
+ commentText: string;
+ commentType: EuiCommentProps['type'];
+ displayComment: boolean;
+ displayResponseEvent: boolean;
+ iconType: IconType;
+ isResponseEvent: boolean;
+ isSuccessful: boolean;
+ responseEventTitle: string;
+ username: string | React.ReactNode;
+} => {
+ return useMemo(() => {
+ let iconType: IconType = 'dot';
+ let commentType: EuiCommentProps['type'] = 'update';
+ let commentText: string = '';
+ let avatarSize: EuiAvatarProps['size'] = 's';
+ let isIsolateAction: boolean = false;
+ let isResponseEvent: boolean = false;
+ let isSuccessful: boolean = false;
+ let displayComment: boolean = false;
+ let displayResponseEvent: boolean = true;
+ let username: EuiCommentProps['username'] = '';
+
+ if (logEntry.type === 'action') {
+ avatarSize = 'm';
+ commentType = 'regular';
+ commentText = logEntry.item.data.data.comment ?? '';
+ displayResponseEvent = false;
+ iconType = 'lockOpen';
+ username = logEntry.item.data.user_id;
+ if (logEntry.item.data.data) {
+ const data = logEntry.item.data.data;
+ if (data.command === 'isolate') {
+ iconType = 'lock';
+ isIsolateAction = true;
+ }
+ if (data.comment) {
+ displayComment = true;
+ }
+ }
+ } else if (logEntry.type === 'response') {
+ isResponseEvent = true;
+ if (logEntry.item.data.action_data.command === 'isolate') {
+ isIsolateAction = true;
+ }
+ if (!!logEntry.item.data.completed_at && !logEntry.item.data.error) {
+ isSuccessful = true;
+ }
+ }
+
+ const actionEventTitle = isIsolateAction
+ ? i18.ACTIVITY_LOG.LogEntry.action.isolatedAction
+ : i18.ACTIVITY_LOG.LogEntry.action.unisolatedAction;
+
+ const getResponseEventTitle = () => {
+ if (isIsolateAction) {
+ if (isSuccessful) {
+ return i18.ACTIVITY_LOG.LogEntry.response.isolationSuccessful;
+ } else {
+ return i18.ACTIVITY_LOG.LogEntry.response.isolationSuccessful;
+ }
+ } else {
+ if (isSuccessful) {
+ return i18.ACTIVITY_LOG.LogEntry.response.unisolationSuccessful;
+ } else {
+ return i18.ACTIVITY_LOG.LogEntry.response.unisolationFailed;
+ }
+ }
+ };
+
+ return {
+ actionEventTitle,
+ avatarSize,
+ commentText,
+ commentType,
+ displayComment,
+ displayResponseEvent,
+ iconType,
+ isResponseEvent,
+ isSuccessful,
+ responseEventTitle: getResponseEventTitle(),
+ username,
+ };
+ }, [logEntry]);
+};
+
+const StyledEuiComment = styled(EuiComment)`
+ .euiCommentEvent__headerTimestamp {
+ display: flex;
+ :before {
+ content: '';
+ background-color: ${(props) => props.theme.eui.euiColorInk};
+ display: block;
+ width: ${(props) => props.theme.eui.euiBorderWidthThick};
+ height: ${(props) => props.theme.eui.euiBorderWidthThick};
+ margin: 0 ${(props) => props.theme.eui.euiSizeXS} 0 ${(props) => props.theme.eui.euiSizeS};
+ border-radius: 50%;
+ content: '';
+ margin: 0 8px 0 4px;
+ border-radius: 50%;
+ position: relative;
+ top: 10px;
+ }
}
-);
+`;
+
+export const LogEntry = memo(({ logEntry }: { logEntry: Immutable }) => {
+ const {
+ actionEventTitle,
+ avatarSize,
+ commentText,
+ commentType,
+ displayComment,
+ displayResponseEvent,
+ iconType,
+ isResponseEvent,
+ isSuccessful,
+ responseEventTitle,
+ username,
+ } = useLogEntryUIProps(logEntry);
+
+ return (
+ {displayResponseEvent ? responseEventTitle : actionEventTitle}}
+ timelineIcon={
+
+ }
+ data-test-subj="timelineEntry"
+ >
+ {displayComment ? (
+
+ {commentText}
+
+ ) : undefined}
+
+ );
+});
LogEntry.displayName = 'LogEntry';
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry_timeline_icon.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry_timeline_icon.tsx
new file mode 100644
index 0000000000000..3ff311cd8a139
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry_timeline_icon.tsx
@@ -0,0 +1,42 @@
+/*
+ * 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, { memo } from 'react';
+import { EuiAvatar, EuiAvatarProps } from '@elastic/eui';
+import { useEuiTheme } from '../../../../../../common/lib/theme/use_eui_theme';
+
+export const LogEntryTimelineIcon = memo(
+ ({
+ avatarSize,
+ isResponseEvent,
+ isSuccessful,
+ iconType,
+ }: {
+ avatarSize: EuiAvatarProps['size'];
+ isResponseEvent: boolean;
+ isSuccessful: boolean;
+ iconType: EuiAvatarProps['iconType'];
+ }) => {
+ const euiTheme = useEuiTheme();
+
+ return (
+
+ );
+ }
+);
+
+LogEntryTimelineIcon.displayName = 'LogEntryTimelineIcon';
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx
index 50c91730e332c..4395e3965ea00 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx
@@ -7,21 +7,48 @@
import React, { memo, useCallback } from 'react';
-import { EuiEmptyPrompt, EuiSpacer } from '@elastic/eui';
+import { EuiButton, EuiEmptyPrompt, EuiLoadingContent, EuiSpacer } from '@elastic/eui';
+import { useDispatch } from 'react-redux';
import { LogEntry } from './components/log_entry';
import * as i18 from '../translations';
import { SearchBar } from '../../../../components/search_bar';
-import { Immutable, EndpointAction } from '../../../../../../common/endpoint/types';
+import { Immutable, ActivityLog } from '../../../../../../common/endpoint/types';
import { AsyncResourceState } from '../../../../state';
+import { useEndpointSelector } from '../hooks';
+import { EndpointAction } from '../../store/action';
+import {
+ getActivityLogDataPaging,
+ getActivityLogError,
+ getActivityLogIterableData,
+ getActivityLogRequestLoaded,
+ getActivityLogRequestLoading,
+} from '../../store/selectors';
export const EndpointActivityLog = memo(
- ({ endpointActions }: { endpointActions: AsyncResourceState> }) => {
+ ({ activityLog }: { activityLog: AsyncResourceState> }) => {
+ const activityLogLoading = useEndpointSelector(getActivityLogRequestLoading);
+ const activityLogLoaded = useEndpointSelector(getActivityLogRequestLoaded);
+ const activityLogData = useEndpointSelector(getActivityLogIterableData);
+ const activityLogError = useEndpointSelector(getActivityLogError);
+ const dispatch = useDispatch<(a: EndpointAction) => void>();
+ const { page, pageSize } = useEndpointSelector(getActivityLogDataPaging);
// TODO
const onSearch = useCallback(() => {}, []);
+
+ const getActivityLog = useCallback(() => {
+ dispatch({
+ type: 'appRequestedEndpointActivityLog',
+ payload: {
+ page: page + 1,
+ pageSize,
+ },
+ });
+ }, [dispatch, page, pageSize]);
+
return (
<>
- {endpointActions.type !== 'LoadedResourceState' || !endpointActions.data.length ? (
+ {activityLogLoading || activityLogError ? (
- {endpointActions.data.map((endpointAction) => (
-
- ))}
+ {activityLogLoading ? (
+
+ ) : (
+ activityLogLoaded &&
+ activityLogData.map((logEntry) => (
+
+ ))
+ )}
+
+ {'show more'}
+
>
)}
>
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoints.stories.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoints.stories.tsx
index fccd48cbde67c..d839bbfaae875 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoints.stories.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoints.stories.tsx
@@ -8,7 +8,7 @@
import React, { ComponentType } from 'react';
import moment from 'moment';
-import { EndpointAction, Immutable } from '../../../../../../common/endpoint/types';
+import { ActivityLog, Immutable } from '../../../../../../common/endpoint/types';
import { EndpointDetailsFlyoutTabs } from './components/endpoint_details_tabs';
import { EndpointActivityLog } from './endpoint_activity_log';
import { EndpointDetailsFlyout } from '.';
@@ -17,63 +17,93 @@ import { AsyncResourceState } from '../../../../state';
export const dummyEndpointActivityLog = (
selectedEndpoint: string = ''
-): AsyncResourceState> => ({
+): AsyncResourceState> => ({
type: 'LoadedResourceState',
- data: [
- {
- action_id: '1',
- '@timestamp': moment().subtract(1, 'hours').fromNow().toString(),
- expiration: moment().add(3, 'day').fromNow().toString(),
- type: 'INPUT_ACTION',
- input_type: 'endpoint',
- agents: [`${selectedEndpoint}`],
- user_id: 'sys',
- data: {
- command: 'isolate',
+ data: {
+ total: 20,
+ page: 1,
+ pageSize: 50,
+ data: [
+ {
+ type: 'action',
+ item: {
+ id: '',
+ data: {
+ action_id: '1',
+ '@timestamp': moment().subtract(1, 'hours').fromNow().toString(),
+ expiration: moment().add(3, 'day').fromNow().toString(),
+ type: 'INPUT_ACTION',
+ input_type: 'endpoint',
+ agents: [`${selectedEndpoint}`],
+ user_id: 'sys',
+ data: {
+ command: 'isolate',
+ },
+ },
+ },
},
- },
- {
- action_id: '2',
- '@timestamp': moment().subtract(2, 'hours').fromNow().toString(),
- expiration: moment().add(1, 'day').fromNow().toString(),
- type: 'INPUT_ACTION',
- input_type: 'endpoint',
- agents: [`${selectedEndpoint}`],
- user_id: 'ash',
- data: {
- command: 'isolate',
- comment: 'Sem et tortor consequat id porta nibh venenatis cras sed.',
+ {
+ type: 'action',
+ item: {
+ id: '',
+ data: {
+ action_id: '2',
+ '@timestamp': moment().subtract(2, 'hours').fromNow().toString(),
+ expiration: moment().add(1, 'day').fromNow().toString(),
+ type: 'INPUT_ACTION',
+ input_type: 'endpoint',
+ agents: [`${selectedEndpoint}`],
+ user_id: 'ash',
+ data: {
+ command: 'isolate',
+ comment: 'Sem et tortor consequat id porta nibh venenatis cras sed.',
+ },
+ },
+ },
},
- },
- {
- action_id: '3',
- '@timestamp': moment().subtract(4, 'hours').fromNow().toString(),
- expiration: moment().add(1, 'day').fromNow().toString(),
- type: 'INPUT_ACTION',
- input_type: 'endpoint',
- agents: [`${selectedEndpoint}`],
- user_id: 'someone',
- data: {
- command: 'unisolate',
- comment: 'Turpis egestas pretium aenean pharetra.',
+ {
+ type: 'action',
+ item: {
+ id: '',
+ data: {
+ action_id: '3',
+ '@timestamp': moment().subtract(4, 'hours').fromNow().toString(),
+ expiration: moment().add(1, 'day').fromNow().toString(),
+ type: 'INPUT_ACTION',
+ input_type: 'endpoint',
+ agents: [`${selectedEndpoint}`],
+ user_id: 'someone',
+ data: {
+ command: 'unisolate',
+ comment: 'Turpis egestas pretium aenean pharetra.',
+ },
+ },
+ },
},
- },
- {
- action_id: '4',
- '@timestamp': moment().subtract(1, 'day').fromNow().toString(),
- expiration: moment().add(3, 'day').fromNow().toString(),
- type: 'INPUT_ACTION',
- input_type: 'endpoint',
- agents: [`${selectedEndpoint}`],
- user_id: 'ash',
- data: {
- command: 'isolate',
- comment:
- 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, \
- sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ {
+ type: 'action',
+ item: {
+ id: '',
+ data: {
+ action_id: '4',
+ '@timestamp': moment().subtract(1, 'day').fromNow().toString(),
+ expiration: moment().add(3, 'day').fromNow().toString(),
+ type: 'INPUT_ACTION',
+ input_type: 'endpoint',
+ agents: [`${selectedEndpoint}`],
+ user_id: 'ash',
+ data: {
+ command: 'isolate',
+ comment:
+ 'Lorem \
+ ipsum dolor sit amet, consectetur adipiscing elit, \
+ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ },
+ },
+ },
},
- },
- ],
+ ],
+ },
});
export default {
@@ -100,12 +130,12 @@ export const Tabs = () => (
{
id: 'activity_log',
name: 'Activity Log',
- content: ActivityLog(),
+ content: ActivityLogMarkup(),
},
]}
/>
);
-export const ActivityLog = () => (
-
+export const ActivityLogMarkup = () => (
+
);
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx
index 89c0e3e6a3e06..c39a17e98c76a 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx
@@ -33,7 +33,6 @@ import {
detailsLoading,
getActivityLogData,
getActivityLogError,
- getActivityLogRequestLoading,
showView,
policyResponseConfigurations,
policyResponseActions,
@@ -87,7 +86,6 @@ export const EndpointDetailsFlyout = memo(() => {
} = queryParams;
const activityLog = useEndpointSelector(getActivityLogData);
- const activityLoading = useEndpointSelector(getActivityLogRequestLoading);
const activityError = useEndpointSelector(getActivityLogError);
const hostDetails = useEndpointSelector(detailsData);
const hostDetailsLoading = useEndpointSelector(detailsLoading);
@@ -121,12 +119,8 @@ export const EndpointDetailsFlyout = memo(() => {
},
{
id: EndpointDetailsTabsTypes.activityLog,
- name: i18.ACTIVITY_LOG,
- content: activityLoading ? (
- ContentLoadingMarkup
- ) : (
-
- ),
+ name: i18.ACTIVITY_LOG.tabTitle,
+ content: ,
},
];
@@ -175,7 +169,7 @@ export const EndpointDetailsFlyout = memo(() => {
paddingSize="m"
>
- {hostDetailsLoading || activityLoading ? (
+ {hostDetailsLoading ? (
) : (
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts
index fd2806713183b..1a7889f22db16 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts
@@ -11,9 +11,53 @@ export const OVERVIEW = i18n.translate('xpack.securitySolution.endpointDetails.o
defaultMessage: 'Overview',
});
-export const ACTIVITY_LOG = i18n.translate('xpack.securitySolution.endpointDetails.activityLog', {
- defaultMessage: 'Activity Log',
-});
+export const ACTIVITY_LOG = {
+ tabTitle: i18n.translate('xpack.securitySolution.endpointDetails.activityLog', {
+ defaultMessage: 'Activity Log',
+ }),
+ LogEntry: {
+ action: {
+ isolatedAction: i18n.translate(
+ 'xpack.securitySolution.endpointDetails.activityLog.logEntry.action.isolated',
+ {
+ defaultMessage: 'isolated host',
+ }
+ ),
+ unisolatedAction: i18n.translate(
+ 'xpack.securitySolution.endpointDetails.activityLog.logEntry.action.unisolated',
+ {
+ defaultMessage: 'unisolated host',
+ }
+ ),
+ },
+ response: {
+ isolationSuccessful: i18n.translate(
+ 'xpack.securitySolution.endpointDetails.activityLog.logEntry.response.isolationSuccessful',
+ {
+ defaultMessage: 'host isolation successful',
+ }
+ ),
+ isolationFailed: i18n.translate(
+ 'xpack.securitySolution.endpointDetails.activityLog.logEntry.response.isolationFailed',
+ {
+ defaultMessage: 'host isolation failed',
+ }
+ ),
+ unisolationSuccessful: i18n.translate(
+ 'xpack.securitySolution.endpointDetails.activityLog.logEntry.response.unisolationSuccessful',
+ {
+ defaultMessage: 'host unisolation successful',
+ }
+ ),
+ unisolationFailed: i18n.translate(
+ 'xpack.securitySolution.endpointDetails.activityLog.logEntry.response.unisolationFailed',
+ {
+ defaultMessage: 'host unisolation failed',
+ }
+ ),
+ },
+ },
+};
export const SEARCH_ACTIVITY_LOG = i18n.translate(
'xpack.securitySolution.endpointDetails.activityLog.search',
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts
new file mode 100644
index 0000000000000..9b73721775382
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts
@@ -0,0 +1,186 @@
+/*
+ * 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.
+ */
+
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import { KibanaResponseFactory, RequestHandler, RouteConfig } from 'kibana/server';
+import {
+ elasticsearchServiceMock,
+ httpServerMock,
+ httpServiceMock,
+ loggingSystemMock,
+ savedObjectsClientMock,
+} from 'src/core/server/mocks';
+import {
+ EndpointActionLogRequestParams,
+ EndpointActionLogRequestQuery,
+ EndpointActionLogRequestSchema,
+} from '../../../../common/endpoint/schema/actions';
+import { ENDPOINT_ACTION_LOG_ROUTE } from '../../../../common/endpoint/constants';
+import { parseExperimentalConfigValue } from '../../../../common/experimental_features';
+import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__';
+import { EndpointAppContextService } from '../../endpoint_app_context_services';
+import {
+ createMockEndpointAppContextServiceStartContract,
+ createRouteHandlerContext,
+} from '../../mocks';
+import { registerActionAuditLogRoutes } from './audit_log';
+import uuid from 'uuid';
+import { aMockAction, aMockResponse, MockAction, mockAuditLog, MockResponse } from './mocks';
+import { SecuritySolutionRequestHandlerContext } from '../../../types';
+import { ActivityLog } from '../../../../common/endpoint/types';
+
+describe('Action Log API', () => {
+ describe('schema', () => {
+ it('should require at least 1 agent ID', () => {
+ expect(() => {
+ EndpointActionLogRequestSchema.params.validate({}); // no agent_ids provided
+ }).toThrow();
+ });
+
+ it('should accept a single agent ID', () => {
+ expect(() => {
+ EndpointActionLogRequestSchema.params.validate({ agent_id: uuid.v4() });
+ }).not.toThrow();
+ });
+
+ it('should work without query params', () => {
+ expect(() => {
+ EndpointActionLogRequestSchema.query.validate({});
+ }).not.toThrow();
+ });
+
+ it('should work with query params', () => {
+ expect(() => {
+ EndpointActionLogRequestSchema.query.validate({ page: 10, page_size: 100 });
+ }).not.toThrow();
+ });
+
+ it('should not work without allowed page and page_size params', () => {
+ expect(() => {
+ EndpointActionLogRequestSchema.query.validate({ page_size: 101 });
+ }).toThrow();
+ });
+ });
+
+ describe('response', () => {
+ const mockID = 'XYZABC-000';
+ const actionID = 'some-known-actionid';
+ let endpointAppContextService: EndpointAppContextService;
+
+ // convenience for calling the route and handler for audit log
+ let getActivityLog: (
+ query?: EndpointActionLogRequestQuery
+ ) => Promise>;
+ // convenience for injecting mock responses for actions index and responses
+ let havingActionsAndResponses: (actions: MockAction[], responses: any[]) => void;
+
+ let havingErrors: () => void;
+
+ beforeEach(() => {
+ const esClientMock = elasticsearchServiceMock.createScopedClusterClient();
+ const routerMock = httpServiceMock.createRouter();
+ endpointAppContextService = new EndpointAppContextService();
+ endpointAppContextService.start(createMockEndpointAppContextServiceStartContract());
+
+ registerActionAuditLogRoutes(routerMock, {
+ logFactory: loggingSystemMock.create(),
+ service: endpointAppContextService,
+ config: () => Promise.resolve(createMockConfig()),
+ experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental),
+ });
+
+ getActivityLog = async (query?: any): Promise> => {
+ const req = httpServerMock.createKibanaRequest({
+ params: { agent_id: mockID },
+ query,
+ });
+ const mockResponse = httpServerMock.createResponseFactory();
+ const [, routeHandler]: [
+ RouteConfig,
+ RequestHandler<
+ EndpointActionLogRequestParams,
+ EndpointActionLogRequestQuery,
+ unknown,
+ SecuritySolutionRequestHandlerContext
+ >
+ ] = routerMock.get.mock.calls.find(([{ path }]) =>
+ path.startsWith(ENDPOINT_ACTION_LOG_ROUTE)
+ )!;
+ await routeHandler(
+ createRouteHandlerContext(esClientMock, savedObjectsClientMock.create()),
+ req,
+ mockResponse
+ );
+
+ return mockResponse;
+ };
+
+ havingActionsAndResponses = (actions: MockAction[], responses: MockResponse[]) => {
+ const actionsData = actions.map((a) => ({
+ _index: '.fleet-actions-7',
+ _source: a.build(),
+ }));
+ const responsesData = responses.map((r) => ({
+ _index: '.ds-.fleet-actions-results-2021.06.09-000001',
+ _source: r.build(),
+ }));
+ const mockResult = mockAuditLog([...actionsData, ...responsesData]);
+ esClientMock.asCurrentUser.search = jest
+ .fn()
+ .mockImplementationOnce(() => Promise.resolve(mockResult));
+ };
+
+ havingErrors = () => {
+ esClientMock.asCurrentUser.search = jest.fn().mockImplementationOnce(() =>
+ Promise.resolve(() => {
+ throw new Error();
+ })
+ );
+ };
+ });
+
+ afterEach(() => {
+ endpointAppContextService.stop();
+ });
+
+ it('should return an empty array when nothing in audit log', async () => {
+ havingActionsAndResponses([], []);
+ const response = await getActivityLog();
+ expect(response.ok).toBeCalled();
+ expect((response.ok.mock.calls[0][0]?.body as ActivityLog).data).toHaveLength(0);
+ });
+
+ it('should have actions and action responses', async () => {
+ havingActionsAndResponses(
+ [
+ aMockAction().withAgent(mockID).withAction('isolate'),
+ aMockAction().withAgent(mockID).withAction('unisolate'),
+ aMockAction().withAgent(mockID).withAction('isolate'),
+ ],
+ [aMockResponse(actionID, mockID), aMockResponse(actionID, mockID)]
+ );
+ const response = await getActivityLog();
+ const responseBody = response.ok.mock.calls[0][0]?.body as ActivityLog;
+
+ expect(response.ok).toBeCalled();
+ expect(responseBody.data).toHaveLength(5);
+ expect(responseBody.data.filter((x: any) => x.type === 'response')).toHaveLength(2);
+ expect(responseBody.data.filter((x: any) => x.type === 'action')).toHaveLength(3);
+ });
+
+ it('should throw errors when no results for some agentID', async () => {
+ havingErrors();
+
+ try {
+ await getActivityLog();
+ } catch (error) {
+ expect(error.message).toEqual(`Error fetching actions log for agent_id ${mockID}`);
+ }
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log_handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log_handler.ts
index fdbb9608463e9..b0cea299af60d 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log_handler.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log_handler.ts
@@ -5,55 +5,34 @@
* 2.0.
*/
-import { TypeOf } from '@kbn/config-schema';
import { RequestHandler } from 'kibana/server';
-import { AGENT_ACTIONS_INDEX } from '../../../../../fleet/common';
-import { EndpointActionLogRequestSchema } from '../../../../common/endpoint/schema/actions';
-
+import {
+ EndpointActionLogRequestParams,
+ EndpointActionLogRequestQuery,
+} from '../../../../common/endpoint/schema/actions';
+import { getAuditLogResponse } from './service';
import { SecuritySolutionRequestHandlerContext } from '../../../types';
import { EndpointAppContext } from '../../types';
export const actionsLogRequestHandler = (
endpointContext: EndpointAppContext
): RequestHandler<
- TypeOf,
- unknown,
+ EndpointActionLogRequestParams,
+ EndpointActionLogRequestQuery,
unknown,
SecuritySolutionRequestHandlerContext
> => {
const logger = endpointContext.logFactory.get('audit_log');
+
return async (context, req, res) => {
- const esClient = context.core.elasticsearch.client.asCurrentUser;
- let result;
- try {
- result = await esClient.search({
- index: AGENT_ACTIONS_INDEX,
- body: {
- query: {
- match: {
- agents: req.params.agent_id,
- },
- },
- sort: [
- {
- '@timestamp': {
- order: 'desc',
- },
- },
- ],
- },
- });
- } catch (error) {
- logger.error(error);
- throw error;
- }
- if (result?.statusCode !== 200) {
- logger.error(`Error fetching actions log for agent_id ${req.params.agent_id}`);
- throw new Error(`Error fetching actions log for agent_id ${req.params.agent_id}`);
- }
+ const {
+ params: { agent_id: elasticAgentId },
+ query: { page, page_size: pageSize },
+ } = req;
+ const body = await getAuditLogResponse({ elasticAgentId, page, pageSize, context, logger });
return res.ok({
- body: result.body.hits.hits.map((e) => e._source),
+ body,
});
};
};
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/mocks.ts
index 34f7d140a78de..f74ae07fdfac4 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/mocks.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/mocks.ts
@@ -18,6 +18,29 @@ import {
ISOLATION_ACTIONS,
} from '../../../../common/endpoint/types';
+export const mockAuditLog = (results: any = []): ApiResponse => {
+ return {
+ body: {
+ hits: {
+ total: results.length,
+ hits: results.map((a: any) => {
+ const _index = a._index;
+ delete a._index;
+ const _source = a;
+ return {
+ _index,
+ _source,
+ };
+ }),
+ },
+ },
+ statusCode: 200,
+ headers: {},
+ warnings: [],
+ meta: {} as any,
+ };
+};
+
export const mockSearchResult = (results: any = []): ApiResponse => {
return {
body: {
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/service.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/service.ts
new file mode 100644
index 0000000000000..20b29694a1df1
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/service.ts
@@ -0,0 +1,110 @@
+/*
+ * 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 { Logger } from 'kibana/server';
+import type { estypes } from '@elastic/elasticsearch';
+import { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '../../../../../fleet/common';
+import { SecuritySolutionRequestHandlerContext } from '../../../types';
+
+export const getAuditLogESQuery = ({
+ elasticAgentId,
+ from,
+ size,
+}: {
+ elasticAgentId: string;
+ from: number;
+ size: number;
+}): estypes.SearchRequest => {
+ return {
+ index: [AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX],
+ size,
+ from,
+ body: {
+ query: {
+ bool: {
+ should: [
+ { terms: { agents: [elasticAgentId] } },
+ { terms: { agent_id: [elasticAgentId] } },
+ ],
+ },
+ },
+ sort: [
+ {
+ '@timestamp': {
+ order: 'desc',
+ },
+ },
+ ],
+ },
+ };
+};
+
+export const getAuditLogResponse = async ({
+ elasticAgentId,
+ page,
+ pageSize,
+ context,
+ logger,
+}: {
+ elasticAgentId: string;
+ page: number;
+ pageSize: number;
+ context: SecuritySolutionRequestHandlerContext;
+ logger: Logger;
+}): Promise<{
+ total: number;
+ page: number;
+ pageSize: number;
+ data: Array<{
+ type: 'action' | 'response';
+ item: {
+ id: string;
+ data: unknown;
+ };
+ }>;
+}> => {
+ const size = pageSize;
+ const from = page <= 1 ? 0 : page * pageSize - pageSize + 1;
+
+ const options = {
+ headers: {
+ 'X-elastic-product-origin': 'fleet',
+ },
+ ignore: [404],
+ };
+ const esClient = context.core.elasticsearch.client.asCurrentUser;
+ let result;
+ const params = getAuditLogESQuery({
+ elasticAgentId,
+ from,
+ size,
+ });
+
+ try {
+ result = await esClient.search(params, options);
+ } catch (error) {
+ logger.error(error);
+ throw error;
+ }
+ if (result?.statusCode !== 200) {
+ logger.error(`Error fetching actions log for agent_id ${elasticAgentId}`);
+ throw new Error(`Error fetching actions log for agent_id ${elasticAgentId}`);
+ }
+
+ return {
+ total:
+ typeof result.body.hits.total === 'number'
+ ? result.body.hits.total
+ : result.body.hits.total.value,
+ page,
+ pageSize,
+ data: result.body.hits.hits.map((e) => ({
+ type: e._index.startsWith('.fleet-actions') ? 'action' : 'response',
+ item: { id: e._id, data: e._source },
+ })),
+ };
+};
From e0a1a34fbc5a92cae76638514779a89cad853fa1 Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Mon, 14 Jun 2021 15:57:03 +0200
Subject: [PATCH 02/91] [Uptime] Align synthetics report types (#101855)
---
.../PageLoadDistribution/index.tsx | 2 +-
.../components/filter_label.test.tsx | 8 +--
.../apm/service_latency_config.ts | 5 +-
.../apm/service_throughput_config.ts | 57 ---------------
.../configurations/constants/constants.ts | 34 +--------
.../constants/field_names/synthetics.ts | 8 +++
.../configurations/constants/labels.ts | 55 +++------------
.../configurations/default_configs.ts | 64 +++++++----------
.../configurations/lens_attributes.test.ts | 6 +-
.../configurations/lens_attributes.ts | 19 +++--
.../logs/logs_frequency_config.ts | 41 -----------
.../metrics/cpu_usage_config.ts | 11 +--
.../metrics/memory_usage_config.ts | 11 +--
.../metrics/network_activity_config.ts | 11 +--
.../rum/core_web_vitals_config.ts | 5 +-
..._config.ts => data_distribution_config.ts} | 5 +-
...ends_config.ts => kpi_over_time_config.ts} | 5 +-
..._config.ts => data_distribution_config.ts} | 25 ++++---
.../synthetics/kpi_over_time_config.ts | 69 +++++++++++++++++++
.../synthetics/monitor_pings_config.ts | 50 --------------
.../exploratory_view.test.tsx | 2 +-
.../exploratory_view/exploratory_view.tsx | 9 ++-
.../exploratory_view/header/header.test.tsx | 2 +-
.../hooks/use_lens_attributes.ts | 13 ++--
.../shared/exploratory_view/rtl_helpers.tsx | 2 +-
.../columns/data_types_col.test.tsx | 2 +-
.../series_builder/columns/data_types_col.tsx | 3 +-
.../columns/operation_type_select.tsx | 6 ++
.../columns/report_breakdowns.test.tsx | 8 +--
.../columns/report_definition_col.test.tsx | 8 +--
.../columns/report_filters.test.tsx | 4 +-
.../columns/report_types_col.test.tsx | 8 +--
.../series_builder/series_builder.tsx | 26 ++-----
.../series_date_picker.test.tsx | 10 +--
.../series_editor/chart_edit_options.tsx | 2 +-
.../series_editor/columns/breakdowns.test.tsx | 7 +-
.../series_editor/columns/chart_options.tsx | 7 +-
.../series_editor/columns/series_actions.tsx | 3 +-
.../series_editor/selected_filters.test.tsx | 5 +-
.../series_editor/series_editor.tsx | 30 ++++----
.../shared/exploratory_view/types.ts | 14 +---
.../common/charts/ping_histogram.tsx | 2 +-
.../monitor_duration_container.tsx | 2 +-
43 files changed, 247 insertions(+), 419 deletions(-)
delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_throughput_config.ts
create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/field_names/synthetics.ts
delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs/logs_frequency_config.ts
rename x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/{performance_dist_config.ts => data_distribution_config.ts} (94%)
rename x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/{kpi_trends_config.ts => kpi_over_time_config.ts} (95%)
rename x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/{monitor_duration_config.ts => data_distribution_config.ts} (61%)
create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts
delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_pings_config.ts
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx
index a8435beca1e4a..adfb45303a4f3 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx
@@ -89,7 +89,7 @@ export function PageLoadDistribution() {
{
[`${serviceName}-page-views`]: {
dataType: 'ux',
- reportType: 'pld',
+ reportType: 'dist',
time: { from: rangeFrom!, to: rangeTo! },
reportDefinitions: {
'service.name': serviceName as string[],
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx
index c042ba9d0bcf8..7c772cb8dbdbc 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/filter_label.test.tsx
@@ -26,7 +26,7 @@ describe('FilterLabel', function () {
value={'elastic-co'}
label={'Web Application'}
negate={false}
- seriesId={'kpi-trends'}
+ seriesId={'kpi-over-time'}
removeFilter={jest.fn()}
/>
);
@@ -49,7 +49,7 @@ describe('FilterLabel', function () {
value={'elastic-co'}
label={'Web Application'}
negate={false}
- seriesId={'kpi-trends'}
+ seriesId={'kpi-over-time'}
removeFilter={removeFilter}
/>
);
@@ -71,7 +71,7 @@ describe('FilterLabel', function () {
value={'elastic-co'}
label={'Web Application'}
negate={false}
- seriesId={'kpi-trends'}
+ seriesId={'kpi-over-time'}
removeFilter={removeFilter}
/>
);
@@ -96,7 +96,7 @@ describe('FilterLabel', function () {
value={'elastic-co'}
label={'Web Application'}
negate={true}
- seriesId={'kpi-trends'}
+ seriesId={'kpi-over-time'}
removeFilter={jest.fn()}
/>
);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_latency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_latency_config.ts
index 1c2627dac30e7..7c3abba3e5b05 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_latency_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_latency_config.ts
@@ -10,10 +10,9 @@ import { FieldLabels } from '../constants';
import { buildPhraseFilter } from '../utils';
import { TRANSACTION_DURATION } from '../constants/elasticsearch_fieldnames';
-export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
+export function getServiceLatencyLensConfig({ indexPattern }: ConfigProps): DataSeries {
return {
- id: seriesId,
- reportType: 'service-latency',
+ reportType: 'kpi-over-time',
defaultSeriesType: 'line',
seriesTypes: ['line', 'bar'],
xAxisColumn: {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_throughput_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_throughput_config.ts
deleted file mode 100644
index 2de2cbdfd75a6..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_throughput_config.ts
+++ /dev/null
@@ -1,57 +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 { ConfigProps, DataSeries } from '../../types';
-import { FieldLabels } from '../constants/constants';
-import { buildPhraseFilter } from '../utils';
-import { TRANSACTION_DURATION } from '../constants/elasticsearch_fieldnames';
-
-export function getServiceThroughputLensConfig({
- seriesId,
- indexPattern,
-}: ConfigProps): DataSeries {
- return {
- id: seriesId,
- reportType: 'service-throughput',
- defaultSeriesType: 'line',
- seriesTypes: ['line', 'bar'],
- xAxisColumn: {
- sourceField: '@timestamp',
- },
- yAxisColumns: [
- {
- operationType: 'average',
- sourceField: 'transaction.duration.us',
- label: 'Throughput',
- },
- ],
- hasOperationType: true,
- defaultFilters: [
- 'user_agent.name',
- 'user_agent.os.name',
- 'client.geo.country_name',
- 'user_agent.device.name',
- ],
- breakdowns: [
- 'user_agent.name',
- 'user_agent.os.name',
- 'client.geo.country_name',
- 'user_agent.device.name',
- ],
- filters: buildPhraseFilter('transaction.type', 'request', indexPattern),
- labels: { ...FieldLabels, [TRANSACTION_DURATION]: 'Throughput' },
- reportDefinitions: [
- {
- field: 'service.name',
- required: true,
- },
- {
- field: 'service.environment',
- },
- ],
- };
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts
index e1142a071aab5..26459e676de08 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { AppDataType, ReportViewTypeId } from '../../types';
+import { ReportViewTypeId } from '../../types';
import { CLS_FIELD, FCP_FIELD, FID_FIELD, LCP_FIELD, TBT_FIELD } from './elasticsearch_fieldnames';
import {
AGENT_HOST_LABEL,
@@ -13,7 +13,6 @@ import {
BROWSER_VERSION_LABEL,
CLS_LABEL,
CORE_WEB_VITALS_LABEL,
- CPU_USAGE_LABEL,
DEVICE_LABEL,
ENVIRONMENT_LABEL,
FCP_LABEL,
@@ -23,25 +22,18 @@ import {
KPI_LABEL,
LCP_LABEL,
LOCATION_LABEL,
- LOGS_FREQUENCY_LABEL,
- MEMORY_USAGE_LABEL,
METRIC_LABEL,
- MONITOR_DURATION_LABEL,
MONITOR_ID_LABEL,
MONITOR_NAME_LABEL,
MONITOR_STATUS_LABEL,
MONITOR_TYPE_LABEL,
- NETWORK_ACTIVITY_LABEL,
OBSERVER_LOCATION_LABEL,
OS_LABEL,
PERF_DIST_LABEL,
PORT_LABEL,
- SERVICE_LATENCY_LABEL,
SERVICE_NAME_LABEL,
- SERVICE_THROUGHPUT_LABEL,
TAGS_LABEL,
TBT_LABEL,
- UPTIME_PINGS_LABEL,
URL_LABEL,
} from './labels';
@@ -83,33 +75,11 @@ export const FieldLabels: Record = {
};
export const DataViewLabels: Record = {
- pld: PERF_DIST_LABEL,
- upd: MONITOR_DURATION_LABEL,
- upp: UPTIME_PINGS_LABEL,
- svl: SERVICE_LATENCY_LABEL,
+ dist: PERF_DIST_LABEL,
kpi: KIP_OVER_TIME_LABEL,
- tpt: SERVICE_THROUGHPUT_LABEL,
- cpu: CPU_USAGE_LABEL,
- logs: LOGS_FREQUENCY_LABEL,
- mem: MEMORY_USAGE_LABEL,
- nwk: NETWORK_ACTIVITY_LABEL,
cwv: CORE_WEB_VITALS_LABEL,
};
-export const ReportToDataTypeMap: Record = {
- upd: 'synthetics',
- upp: 'synthetics',
- tpt: 'apm',
- svl: 'apm',
- kpi: 'ux',
- pld: 'ux',
- nwk: 'infra_metrics',
- mem: 'infra_metrics',
- logs: 'infra_logs',
- cpu: 'infra_metrics',
- cwv: 'ux',
-};
-
export const USE_BREAK_DOWN_COLUMN = 'USE_BREAK_DOWN_COLUMN';
export const FILTER_RECORDS = 'FILTER_RECORDS';
export const OPERATION_COLUMN = 'operation';
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/field_names/synthetics.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/field_names/synthetics.ts
new file mode 100644
index 0000000000000..edf8b7fb9d741
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/field_names/synthetics.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 const MONITOR_DURATION_US = 'monitor.duration.us';
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts
index 92150a76319f8..b5816daa419df 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts
@@ -100,6 +100,10 @@ export const PAGES_LOADED_LABEL = i18n.translate(
}
);
+export const PINGS_LABEL = i18n.translate('xpack.observability.expView.fieldLabels.pings', {
+ defaultMessage: 'Pings',
+});
+
export const MONITOR_ID_LABEL = i18n.translate(
'xpack.observability.expView.fieldLabels.monitorId',
{
@@ -165,42 +169,6 @@ export const PERF_DIST_LABEL = i18n.translate(
}
);
-export const MONITOR_DURATION_LABEL = i18n.translate(
- 'xpack.observability.expView.fieldLabels.monitorDuration',
- {
- defaultMessage: 'Uptime monitor duration',
- }
-);
-
-export const UPTIME_PINGS_LABEL = i18n.translate('xpack.observability.expView.fieldLabels.pings', {
- defaultMessage: 'Uptime pings',
-});
-
-export const SERVICE_LATENCY_LABEL = i18n.translate(
- 'xpack.observability.expView.fieldLabels.serviceLatency',
- {
- defaultMessage: 'APM Service latency',
- }
-);
-
-export const SERVICE_THROUGHPUT_LABEL = i18n.translate(
- 'xpack.observability.expView.fieldLabels.serviceThroughput',
- {
- defaultMessage: 'APM Service throughput',
- }
-);
-
-export const CPU_USAGE_LABEL = i18n.translate('xpack.observability.expView.fieldLabels.cpuUsage', {
- defaultMessage: 'System CPU usage',
-});
-
-export const NETWORK_ACTIVITY_LABEL = i18n.translate(
- 'xpack.observability.expView.fieldLabels.networkActivity',
- {
- defaultMessage: 'Network activity',
- }
-);
-
export const CORE_WEB_VITALS_LABEL = i18n.translate(
'xpack.observability.expView.fieldLabels.coreWebVitals',
{
@@ -215,13 +183,6 @@ export const MEMORY_USAGE_LABEL = i18n.translate(
}
);
-export const LOGS_FREQUENCY_LABEL = i18n.translate(
- 'xpack.observability.expView.fieldLabels.logsFrequency',
- {
- defaultMessage: 'Logs frequency',
- }
-);
-
export const KIP_OVER_TIME_LABEL = i18n.translate(
'xpack.observability.expView.fieldLabels.kpiOverTime',
{
@@ -243,10 +204,10 @@ export const WEB_APPLICATION_LABEL = i18n.translate(
}
);
-export const UP_LABEL = i18n.translate('xpack.observability.expView.fieldLabels.up', {
- defaultMessage: 'Up',
+export const UP_LABEL = i18n.translate('xpack.observability.expView.fieldLabels.upPings', {
+ defaultMessage: 'Up Pings',
});
-export const DOWN_LABEL = i18n.translate('xpack.observability.expView.fieldLabels.down', {
- defaultMessage: 'Down',
+export const DOWN_LABEL = i18n.translate('xpack.observability.expView.fieldLabels.downPings', {
+ defaultMessage: 'Down Pings',
});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts
index 797ee0c2e0977..13a7900ef5764 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts
@@ -5,51 +5,37 @@
* 2.0.
*/
-import { ReportViewTypes } from '../types';
-import { getPerformanceDistLensConfig } from './rum/performance_dist_config';
-import { getMonitorDurationConfig } from './synthetics/monitor_duration_config';
-import { getServiceLatencyLensConfig } from './apm/service_latency_config';
-import { getMonitorPingsConfig } from './synthetics/monitor_pings_config';
-import { getServiceThroughputLensConfig } from './apm/service_throughput_config';
-import { getKPITrendsLensConfig } from './rum/kpi_trends_config';
-import { getCPUUsageLensConfig } from './metrics/cpu_usage_config';
-import { getMemoryUsageLensConfig } from './metrics/memory_usage_config';
-import { getNetworkActivityLensConfig } from './metrics/network_activity_config';
-import { getLogsFrequencyLensConfig } from './logs/logs_frequency_config';
-import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns';
+import { AppDataType, ReportViewTypes } from '../types';
+import { getRumDistributionConfig } from './rum/data_distribution_config';
+import { getSyntheticsDistributionConfig } from './synthetics/data_distribution_config';
+import { getSyntheticsKPIConfig } from './synthetics/kpi_over_time_config';
+import { getKPITrendsLensConfig } from './rum/kpi_over_time_config';
+import { IndexPattern } from '../../../../../../../../src/plugins/data/common';
import { getCoreWebVitalsConfig } from './rum/core_web_vitals_config';
interface Props {
reportType: keyof typeof ReportViewTypes;
- seriesId: string;
- indexPattern: IIndexPattern;
+ indexPattern: IndexPattern;
+ dataType: AppDataType;
}
-export const getDefaultConfigs = ({ reportType, seriesId, indexPattern }: Props) => {
- switch (ReportViewTypes[reportType]) {
- case 'page-load-dist':
- return getPerformanceDistLensConfig({ seriesId, indexPattern });
- case 'kpi-trends':
- return getKPITrendsLensConfig({ seriesId, indexPattern });
- case 'core-web-vitals':
- return getCoreWebVitalsConfig({ seriesId, indexPattern });
- case 'uptime-duration':
- return getMonitorDurationConfig({ seriesId, indexPattern });
- case 'uptime-pings':
- return getMonitorPingsConfig({ seriesId, indexPattern });
- case 'service-latency':
- return getServiceLatencyLensConfig({ seriesId, indexPattern });
- case 'service-throughput':
- return getServiceThroughputLensConfig({ seriesId, indexPattern });
- case 'cpu-usage':
- return getCPUUsageLensConfig({ seriesId });
- case 'memory-usage':
- return getMemoryUsageLensConfig({ seriesId });
- case 'network-activity':
- return getNetworkActivityLensConfig({ seriesId });
- case 'logs-frequency':
- return getLogsFrequencyLensConfig({ seriesId });
+export const getDefaultConfigs = ({ reportType, dataType, indexPattern }: Props) => {
+ switch (dataType) {
+ case 'ux':
+ if (reportType === 'dist') {
+ return getRumDistributionConfig({ indexPattern });
+ }
+ if (reportType === 'cwv') {
+ return getCoreWebVitalsConfig({ indexPattern });
+ }
+ return getKPITrendsLensConfig({ indexPattern });
+ case 'synthetics':
+ if (reportType === 'dist') {
+ return getSyntheticsDistributionConfig({ indexPattern });
+ }
+ return getSyntheticsKPIConfig({ indexPattern });
+
default:
- return getKPITrendsLensConfig({ seriesId, indexPattern });
+ return getKPITrendsLensConfig({ indexPattern });
}
};
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts
index 6976b55921b09..8b21df64a3c91 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts
@@ -15,9 +15,9 @@ describe('Lens Attribute', () => {
mockAppIndexPattern();
const reportViewConfig = getDefaultConfigs({
- reportType: 'pld',
+ reportType: 'dist',
+ dataType: 'ux',
indexPattern: mockIndexPattern,
- seriesId: 'series-id',
});
let lnsAttr: LensAttributes;
@@ -73,6 +73,7 @@ describe('Lens Attribute', () => {
readFromDocValues: true,
},
fieldName: 'transaction.duration.us',
+ columnLabel: 'Page load time',
})
);
});
@@ -95,6 +96,7 @@ describe('Lens Attribute', () => {
readFromDocValues: true,
},
fieldName: LCP_FIELD,
+ columnLabel: 'Largest contentful paint',
})
);
});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
index 89fc9ca5fcc58..bc535e29ab435 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts
@@ -55,6 +55,7 @@ export const parseCustomFieldName = (
let fieldName = sourceField;
let columnType;
let columnFilters;
+ let columnLabel;
const rdf = reportViewConfig.reportDefinitions ?? [];
@@ -69,15 +70,17 @@ export const parseCustomFieldName = (
);
columnType = currField?.columnType;
columnFilters = currField?.columnFilters;
+ columnLabel = currField?.label;
}
} else if (customField.options?.[0].field || customField.options?.[0].id) {
fieldName = customField.options?.[0].field || customField.options?.[0].id;
columnType = customField.options?.[0].columnType;
columnFilters = customField.options?.[0].columnFilters;
+ columnLabel = customField.options?.[0].label;
}
}
- return { fieldName, columnType, columnFilters };
+ return { fieldName, columnType, columnFilters, columnLabel };
};
export class LensAttributes {
@@ -260,12 +263,14 @@ export class LensAttributes {
label?: string,
colIndex?: number
) {
- const { fieldMeta, columnType, fieldName, columnFilters } = this.getFieldMeta(sourceField);
+ const { fieldMeta, columnType, fieldName, columnFilters, columnLabel } = this.getFieldMeta(
+ sourceField
+ );
const { type: fieldType } = fieldMeta ?? {};
if (fieldName === 'Records' || columnType === FILTER_RECORDS) {
return this.getRecordsColumn(
- label,
+ columnLabel || label,
colIndex !== undefined ? columnFilters?.[colIndex] : undefined
);
}
@@ -274,7 +279,7 @@ export class LensAttributes {
return this.getDateHistogramColumn(fieldName);
}
if (fieldType === 'number') {
- return this.getNumberColumn(fieldName, columnType, operationType, label);
+ return this.getNumberColumn(fieldName, columnType, operationType, columnLabel || label);
}
// FIXME review my approach again
@@ -286,11 +291,13 @@ export class LensAttributes {
}
getFieldMeta(sourceField: string) {
- const { fieldName, columnType, columnFilters } = this.getCustomFieldName(sourceField);
+ const { fieldName, columnType, columnFilters, columnLabel } = this.getCustomFieldName(
+ sourceField
+ );
const fieldMeta = this.indexPattern.getFieldByName(fieldName);
- return { fieldMeta, fieldName, columnType, columnFilters };
+ return { fieldMeta, fieldName, columnType, columnFilters, columnLabel };
}
getMainYAxis() {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs/logs_frequency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs/logs_frequency_config.ts
deleted file mode 100644
index 97d915ede01a9..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs/logs_frequency_config.ts
+++ /dev/null
@@ -1,41 +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 { DataSeries } from '../../types';
-import { FieldLabels } from '../constants';
-
-interface Props {
- seriesId: string;
-}
-
-export function getLogsFrequencyLensConfig({ seriesId }: Props): DataSeries {
- return {
- id: seriesId,
- reportType: 'logs-frequency',
- defaultSeriesType: 'line',
- seriesTypes: ['line', 'bar'],
- xAxisColumn: {
- sourceField: '@timestamp',
- },
- yAxisColumns: [
- {
- operationType: 'count',
- },
- ],
- hasOperationType: false,
- defaultFilters: [],
- breakdowns: ['agent.hostname'],
- filters: [],
- labels: { ...FieldLabels },
- reportDefinitions: [
- {
- field: 'agent.hostname',
- required: true,
- },
- ],
- };
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/cpu_usage_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/cpu_usage_config.ts
index 28b381bd12473..2d44e122af82d 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/cpu_usage_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/cpu_usage_config.ts
@@ -5,17 +5,12 @@
* 2.0.
*/
-import { DataSeries } from '../../types';
+import { DataSeries, ConfigProps } from '../../types';
import { FieldLabels } from '../constants';
-interface Props {
- seriesId: string;
-}
-
-export function getCPUUsageLensConfig({ seriesId }: Props): DataSeries {
+export function getCPUUsageLensConfig({}: ConfigProps): DataSeries {
return {
- id: seriesId,
- reportType: 'cpu-usage',
+ reportType: 'kpi-over-time',
defaultSeriesType: 'line',
seriesTypes: ['line', 'bar'],
xAxisColumn: {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/memory_usage_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/memory_usage_config.ts
index 2bd0e4b032778..deaa551dce657 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/memory_usage_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/memory_usage_config.ts
@@ -5,17 +5,12 @@
* 2.0.
*/
-import { DataSeries } from '../../types';
+import { DataSeries, ConfigProps } from '../../types';
import { FieldLabels } from '../constants';
-interface Props {
- seriesId: string;
-}
-
-export function getMemoryUsageLensConfig({ seriesId }: Props): DataSeries {
+export function getMemoryUsageLensConfig({}: ConfigProps): DataSeries {
return {
- id: seriesId,
- reportType: 'memory-usage',
+ reportType: 'kpi-over-time',
defaultSeriesType: 'line',
seriesTypes: ['line', 'bar'],
xAxisColumn: {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/network_activity_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/network_activity_config.ts
index 924701bc13490..d27cdba207d63 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/network_activity_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/network_activity_config.ts
@@ -5,17 +5,12 @@
* 2.0.
*/
-import { DataSeries } from '../../types';
+import { DataSeries, ConfigProps } from '../../types';
import { FieldLabels } from '../constants';
-interface Props {
- seriesId: string;
-}
-
-export function getNetworkActivityLensConfig({ seriesId }: Props): DataSeries {
+export function getNetworkActivityLensConfig({}: ConfigProps): DataSeries {
return {
- id: seriesId,
- reportType: 'network-activity',
+ reportType: 'kpi-over-time',
defaultSeriesType: 'line',
seriesTypes: ['line', 'bar'],
xAxisColumn: {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts
index de9ea12be20cf..e34d8b0dcfdd0 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.ts
@@ -27,13 +27,12 @@ import {
SERVICE_ENVIRONMENT,
} from '../constants/elasticsearch_fieldnames';
-export function getCoreWebVitalsConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
+export function getCoreWebVitalsConfig({ indexPattern }: ConfigProps): DataSeries {
const statusPallete = euiPaletteForStatus(3);
return {
- id: seriesId,
defaultSeriesType: 'bar_horizontal_percentage_stacked',
- reportType: 'kpi-trends',
+ reportType: 'core-web-vitals',
seriesTypes: ['bar_horizontal_percentage_stacked'],
xAxisColumn: {
sourceField: USE_BREAK_DOWN_COLUMN,
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts
similarity index 94%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts
index 4a1521c834806..812f1b2e4cf33 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts
@@ -39,10 +39,9 @@ import {
WEB_APPLICATION_LABEL,
} from '../constants/labels';
-export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
+export function getRumDistributionConfig({ indexPattern }: ConfigProps): DataSeries {
return {
- id: seriesId ?? 'unique-key',
- reportType: 'page-load-dist',
+ reportType: 'data-distribution',
defaultSeriesType: 'line',
seriesTypes: [],
xAxisColumn: {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_over_time_config.ts
similarity index 95%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_over_time_config.ts
index f6c683caaa039..12d66c55c7d00 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_over_time_config.ts
@@ -39,12 +39,11 @@ import {
WEB_APPLICATION_LABEL,
} from '../constants/labels';
-export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
+export function getKPITrendsLensConfig({ indexPattern }: ConfigProps): DataSeries {
return {
- id: seriesId,
defaultSeriesType: 'bar_stacked',
- reportType: 'kpi-trends',
seriesTypes: [],
+ reportType: 'kpi-over-time',
xAxisColumn: {
sourceField: '@timestamp',
},
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_duration_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts
similarity index 61%
rename from x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_duration_config.ts
rename to x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts
index 5e8a43ccf2ef4..854f844db047d 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_duration_config.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts
@@ -6,27 +6,25 @@
*/
import { ConfigProps, DataSeries } from '../../types';
-import { FieldLabels } from '../constants';
+import { FieldLabels, RECORDS_FIELD } from '../constants';
import { buildExistsFilter } from '../utils';
-import { MONITORS_DURATION_LABEL } from '../constants/labels';
+import { MONITORS_DURATION_LABEL, PINGS_LABEL } from '../constants/labels';
-export function getMonitorDurationConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
+export function getSyntheticsDistributionConfig({ indexPattern }: ConfigProps): DataSeries {
return {
- id: seriesId,
- reportType: 'uptime-duration',
+ reportType: 'data-distribution',
defaultSeriesType: 'line',
seriesTypes: [],
xAxisColumn: {
- sourceField: '@timestamp',
+ sourceField: 'performance.metric',
},
yAxisColumns: [
{
- operationType: 'average',
- sourceField: 'monitor.duration.us',
- label: MONITORS_DURATION_LABEL,
+ sourceField: RECORDS_FIELD,
+ label: PINGS_LABEL,
},
],
- hasOperationType: true,
+ hasOperationType: false,
defaultFilters: ['monitor.type', 'observer.geo.name', 'tags'],
breakdowns: [
'observer.geo.name',
@@ -44,6 +42,13 @@ export function getMonitorDurationConfig({ seriesId, indexPattern }: ConfigProps
{
field: 'url.full',
},
+ {
+ field: 'performance.metric',
+ custom: true,
+ options: [
+ { label: 'Monitor duration', id: 'monitor.duration.us', field: 'monitor.duration.us' },
+ ],
+ },
],
labels: { ...FieldLabels, 'monitor.duration.us': MONITORS_DURATION_LABEL },
};
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts
new file mode 100644
index 0000000000000..3e92845436363
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts
@@ -0,0 +1,69 @@
+/*
+ * 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 { ConfigProps, DataSeries } from '../../types';
+import { FieldLabels, OPERATION_COLUMN } from '../constants';
+import { buildExistsFilter } from '../utils';
+import { DOWN_LABEL, MONITORS_DURATION_LABEL, UP_LABEL } from '../constants/labels';
+import { MONITOR_DURATION_US } from '../constants/field_names/synthetics';
+const SUMMARY_UP = 'summary.up';
+const SUMMARY_DOWN = 'summary.down';
+
+export function getSyntheticsKPIConfig({ indexPattern }: ConfigProps): DataSeries {
+ return {
+ reportType: 'kpi-over-time',
+ defaultSeriesType: 'bar_stacked',
+ seriesTypes: [],
+ xAxisColumn: {
+ sourceField: '@timestamp',
+ },
+ yAxisColumns: [
+ {
+ sourceField: 'business.kpi',
+ operationType: 'median',
+ },
+ ],
+ hasOperationType: false,
+ defaultFilters: ['observer.geo.name', 'monitor.type', 'tags'],
+ breakdowns: ['observer.geo.name', 'monitor.type'],
+ filters: [...buildExistsFilter('summary.up', indexPattern)],
+ palette: { type: 'palette', name: 'status' },
+ reportDefinitions: [
+ {
+ field: 'monitor.name',
+ },
+ {
+ field: 'url.full',
+ },
+ {
+ field: 'business.kpi',
+ custom: true,
+ options: [
+ {
+ label: MONITORS_DURATION_LABEL,
+ field: MONITOR_DURATION_US,
+ id: MONITOR_DURATION_US,
+ columnType: OPERATION_COLUMN,
+ },
+ {
+ field: SUMMARY_UP,
+ id: SUMMARY_UP,
+ label: UP_LABEL,
+ columnType: OPERATION_COLUMN,
+ },
+ {
+ field: SUMMARY_DOWN,
+ id: SUMMARY_DOWN,
+ label: DOWN_LABEL,
+ columnType: OPERATION_COLUMN,
+ },
+ ],
+ },
+ ],
+ labels: { ...FieldLabels },
+ };
+}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_pings_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_pings_config.ts
deleted file mode 100644
index 697a940f666f7..0000000000000
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_pings_config.ts
+++ /dev/null
@@ -1,50 +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 { ConfigProps, DataSeries } from '../../types';
-import { FieldLabels } from '../constants';
-import { buildExistsFilter } from '../utils';
-import { DOWN_LABEL, UP_LABEL } from '../constants/labels';
-
-export function getMonitorPingsConfig({ seriesId, indexPattern }: ConfigProps): DataSeries {
- return {
- id: seriesId,
- reportType: 'uptime-pings',
- defaultSeriesType: 'bar_stacked',
- seriesTypes: [],
- xAxisColumn: {
- sourceField: '@timestamp',
- },
- yAxisColumns: [
- {
- operationType: 'sum',
- sourceField: 'summary.up',
- label: UP_LABEL,
- },
- {
- operationType: 'sum',
- sourceField: 'summary.down',
- label: DOWN_LABEL,
- },
- ],
- yTitle: 'Pings',
- hasOperationType: false,
- defaultFilters: ['observer.geo.name', 'monitor.type', 'tags'],
- breakdowns: ['observer.geo.name', 'monitor.type'],
- filters: [...buildExistsFilter('summary.up', indexPattern)],
- palette: { type: 'palette', name: 'status' },
- reportDefinitions: [
- {
- field: 'monitor.name',
- },
- {
- field: 'url.full',
- },
- ],
- labels: { ...FieldLabels },
- };
-}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx
index fc0062694e0a3..487ecdb2bafcc 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx
@@ -52,7 +52,7 @@ describe('ExploratoryView', () => {
data: {
'ux-series': {
dataType: 'ux' as const,
- reportType: 'pld' as const,
+ reportType: 'dist' as const,
breakdown: 'user_agent .name',
reportDefinitions: { 'service.name': ['elastic-co'] },
time: { from: 'now-15m', to: 'now' },
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx
index 7958dca6e396e..329ed20ffed3d 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx
@@ -16,7 +16,6 @@ import { useLensAttributes } from './hooks/use_lens_attributes';
import { EmptyView } from './components/empty_view';
import { TypedLensByValueInput } from '../../../../../lens/public';
import { useAppIndexPatternContext } from './hooks/use_app_index_pattern';
-import { ReportToDataTypeMap } from './configurations/constants';
import { SeriesBuilder } from './series_builder/series_builder';
export function ExploratoryView({
@@ -61,10 +60,10 @@ export function ExploratoryView({
};
useEffect(() => {
- if (series?.reportType || series?.dataType) {
- loadIndexPattern({ dataType: series?.dataType ?? ReportToDataTypeMap[series?.reportType] });
+ if (series?.dataType) {
+ loadIndexPattern({ dataType: series?.dataType });
}
- }, [series?.reportType, series?.dataType, loadIndexPattern]);
+ }, [series?.dataType, loadIndexPattern]);
useEffect(() => {
setLensAttributes(lensAttributesT);
@@ -91,7 +90,7 @@ export function ExploratoryView({
timeRange={series?.time}
attributes={lensAttributes}
onBrushEnd={({ range }) => {
- if (series?.reportType !== 'pld') {
+ if (series?.reportType !== 'dist') {
setSeries(seriesId, {
...series,
time: {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx
index ca9f2c9e73eb8..1dedc4142f174 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.test.tsx
@@ -26,7 +26,7 @@ describe('ExploratoryViewHeader', function () {
data: {
'uptime-pings-histogram': {
dataType: 'synthetics' as const,
- reportType: 'upp' as const,
+ reportType: 'kpi' as const,
breakdown: 'monitor.status',
time: { from: 'now-15m', to: 'now' },
},
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts
index 4e9c360745b6b..1c85bc5089b2a 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts
@@ -42,7 +42,8 @@ export const useLensAttributes = ({
}: Props): TypedLensByValueInput['attributes'] | null => {
const { getSeries } = useSeriesStorage();
const series = getSeries(seriesId);
- const { breakdown, seriesType, operationType, reportType, reportDefinitions = {} } = series ?? {};
+ const { breakdown, seriesType, operationType, reportType, dataType, reportDefinitions = {} } =
+ series ?? {};
const { indexPattern } = useAppIndexPatternContext();
@@ -52,8 +53,8 @@ export const useLensAttributes = ({
}
const dataViewConfig = getDefaultConfigs({
- seriesId,
reportType,
+ dataType,
indexPattern,
});
@@ -78,12 +79,12 @@ export const useLensAttributes = ({
return lensAttributes.getJSON();
}, [
indexPattern,
- breakdown,
- seriesType,
- operationType,
reportType,
reportDefinitions,
- seriesId,
+ dataType,
series.filters,
+ seriesType,
+ operationType,
+ breakdown,
]);
};
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx
index 9118e49a42dfb..ff766f7e6a1cf 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx
@@ -259,7 +259,7 @@ function mockSeriesStorageContext({
}) {
const mockDataSeries = data || {
'performance-distribution': {
- reportType: 'pld',
+ reportType: 'dist',
dataType: 'ux',
breakdown: breakdown || 'user_agent.name',
time: { from: 'now-15m', to: 'now' },
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx
index 51529a3b1ac17..e3c1666c533ef 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx
@@ -37,7 +37,7 @@ describe('DataTypesCol', function () {
data: {
[seriesId]: {
dataType: 'synthetics' as const,
- reportType: 'upp' as const,
+ reportType: 'kpi' as const,
breakdown: 'monitor.status',
time: { from: 'now-15m', to: 'now' },
},
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx
index 08e7f4ddcd3d0..3fe88de518f75 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx
@@ -11,7 +11,6 @@ import styled from 'styled-components';
import { AppDataType } from '../../types';
import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern';
import { useSeriesStorage } from '../../hooks/use_series_storage';
-import { ReportToDataTypeMap } from '../../configurations/constants';
export const dataTypes: Array<{ id: AppDataType; label: string }> = [
{ id: 'synthetics', label: 'Synthetic Monitoring' },
@@ -35,7 +34,7 @@ export function DataTypesCol({ seriesId }: { seriesId: string }) {
}
};
- const selectedDataType = series.dataType ?? ReportToDataTypeMap[series.reportType];
+ const selectedDataType = series.dataType;
return (
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx
index fa273f6180935..fce1383f30f34 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx
@@ -48,6 +48,12 @@ export function OperationTypeSelect({
defaultMessage: 'Median',
}),
},
+ {
+ value: 'sum' as OperationType,
+ inputDisplay: i18n.translate('xpack.observability.expView.operationType.sum', {
+ defaultMessage: 'Sum',
+ }),
+ },
{
value: '75th' as OperationType,
inputDisplay: i18n.translate('xpack.observability.expView.operationType.75thPercentile', {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.test.tsx
index f576862f18e76..805186e877d57 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_breakdowns.test.tsx
@@ -15,8 +15,8 @@ import { USER_AGENT_OS } from '../../configurations/constants/elasticsearch_fiel
describe('Series Builder ReportBreakdowns', function () {
const seriesId = 'test-series-id';
const dataViewSeries = getDefaultConfigs({
- seriesId,
- reportType: 'pld',
+ reportType: 'dist',
+ dataType: 'ux',
indexPattern: mockIndexPattern,
});
@@ -45,7 +45,7 @@ describe('Series Builder ReportBreakdowns', function () {
expect(setSeries).toHaveBeenCalledWith(seriesId, {
breakdown: USER_AGENT_OS,
dataType: 'ux',
- reportType: 'pld',
+ reportType: 'dist',
time: { from: 'now-15m', to: 'now' },
});
});
@@ -67,7 +67,7 @@ describe('Series Builder ReportBreakdowns', function () {
expect(setSeries).toHaveBeenCalledWith(seriesId, {
breakdown: undefined,
dataType: 'ux',
- reportType: 'pld',
+ reportType: 'dist',
time: { from: 'now-15m', to: 'now' },
});
});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx
index fdf6633c0ddb5..8738235f0c54b 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.test.tsx
@@ -22,16 +22,16 @@ describe('Series Builder ReportDefinitionCol', function () {
const seriesId = 'test-series-id';
const dataViewSeries = getDefaultConfigs({
- seriesId,
- reportType: 'pld',
+ reportType: 'dist',
indexPattern: mockIndexPattern,
+ dataType: 'ux',
});
const initSeries = {
data: {
[seriesId]: {
dataType: 'ux' as const,
- reportType: 'pld' as const,
+ reportType: 'dist' as const,
time: { from: 'now-30d', to: 'now' },
reportDefinitions: { [SERVICE_NAME]: ['elastic-co'] },
},
@@ -81,7 +81,7 @@ describe('Series Builder ReportDefinitionCol', function () {
expect(setSeries).toHaveBeenCalledWith(seriesId, {
dataType: 'ux',
reportDefinitions: {},
- reportType: 'pld',
+ reportType: 'dist',
time: { from: 'now-30d', to: 'now' },
});
});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.test.tsx
index dc2dc629cc121..7ca947fed0bc9 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.test.tsx
@@ -15,9 +15,9 @@ describe('Series Builder ReportFilters', function () {
const seriesId = 'test-series-id';
const dataViewSeries = getDefaultConfigs({
- seriesId,
- reportType: 'pld',
+ reportType: 'dist',
indexPattern: mockIndexPattern,
+ dataType: 'ux',
});
it('should render properly', function () {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx
index c721a2fa2fe77..f36d64ca5bbbd 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx
@@ -34,13 +34,13 @@ describe('ReportTypesCol', function () {
);
- fireEvent.click(screen.getByText(/monitor duration/i));
+ fireEvent.click(screen.getByText(/KPI over time/i));
expect(setSeries).toHaveBeenCalledWith(seriesId, {
breakdown: 'user_agent.name',
dataType: 'ux',
reportDefinitions: {},
- reportType: 'upd',
+ reportType: 'kpi',
time: { from: 'now-15m', to: 'now' },
});
expect(setSeries).toHaveBeenCalledTimes(1);
@@ -51,7 +51,7 @@ describe('ReportTypesCol', function () {
data: {
[NEW_SERIES_KEY]: {
dataType: 'synthetics' as const,
- reportType: 'upp' as const,
+ reportType: 'kpi' as const,
breakdown: 'monitor.status',
time: { from: 'now-15m', to: 'now' },
},
@@ -64,7 +64,7 @@ describe('ReportTypesCol', function () {
);
const button = screen.getByRole('button', {
- name: /pings histogram/i,
+ name: /KPI over time/i,
});
expect(button.classList).toContain('euiButton--fill');
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx
index 32f1fb7f7c43b..e24d246d60e58 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx
@@ -21,29 +21,17 @@ import { getDefaultConfigs } from '../configurations/default_configs';
export const ReportTypes: Record> = {
synthetics: [
- { id: 'upd', label: 'Monitor duration' },
- { id: 'upp', label: 'Pings histogram' },
+ { id: 'kpi', label: 'KPI over time' },
+ { id: 'dist', label: 'Performance distribution' },
],
ux: [
- { id: 'pld', label: 'Performance distribution' },
{ id: 'kpi', label: 'KPI over time' },
+ { id: 'dist', label: 'Performance distribution' },
{ id: 'cwv', label: 'Core Web Vitals' },
],
- apm: [
- { id: 'svl', label: 'Latency' },
- { id: 'tpt', label: 'Throughput' },
- ],
- infra_logs: [
- {
- id: 'logs',
- label: 'Logs Frequency',
- },
- ],
- infra_metrics: [
- { id: 'cpu', label: 'CPU usage' },
- { id: 'mem', label: 'Memory usage' },
- { id: 'nwk', label: 'Network activity' },
- ],
+ apm: [],
+ infra_logs: [],
+ infra_metrics: [],
};
export function SeriesBuilder({
@@ -72,7 +60,7 @@ export function SeriesBuilder({
const getDataViewSeries = () => {
return getDefaultConfigs({
- seriesId,
+ dataType,
indexPattern,
reportType: reportType!,
});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx
index 0edc4330ef97a..2b46bb9a8cd62 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx
@@ -17,7 +17,7 @@ describe('SeriesDatePicker', function () {
data: {
'uptime-pings-histogram': {
dataType: 'synthetics' as const,
- reportType: 'upp' as const,
+ reportType: 'dist' as const,
breakdown: 'monitor.status',
time: { from: 'now-30m', to: 'now' },
},
@@ -32,7 +32,7 @@ describe('SeriesDatePicker', function () {
const initSeries = {
data: {
'uptime-pings-histogram': {
- reportType: 'upp' as const,
+ reportType: 'kpi' as const,
dataType: 'synthetics' as const,
breakdown: 'monitor.status',
},
@@ -46,7 +46,7 @@ describe('SeriesDatePicker', function () {
expect(setSeries1).toHaveBeenCalledWith('uptime-pings-histogram', {
breakdown: 'monitor.status',
dataType: 'synthetics' as const,
- reportType: 'upp' as const,
+ reportType: 'kpi' as const,
time: DEFAULT_TIME,
});
});
@@ -56,7 +56,7 @@ describe('SeriesDatePicker', function () {
data: {
'uptime-pings-histogram': {
dataType: 'synthetics' as const,
- reportType: 'upp' as const,
+ reportType: 'kpi' as const,
breakdown: 'monitor.status',
time: { from: 'now-30m', to: 'now' },
},
@@ -79,7 +79,7 @@ describe('SeriesDatePicker', function () {
expect(setSeries).toHaveBeenCalledWith('series-id', {
breakdown: 'monitor.status',
dataType: 'synthetics',
- reportType: 'upp',
+ reportType: 'kpi',
time: { from: 'now/d', to: 'now/d' },
});
expect(setSeries).toHaveBeenCalledTimes(1);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/chart_edit_options.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/chart_edit_options.tsx
index 4bef3e8f71821..a0d2fd86482a5 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/chart_edit_options.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/chart_edit_options.tsx
@@ -23,7 +23,7 @@ export function ChartEditOptions({ series, seriesId, breakdowns }: Props) {
-
+
);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx
index 0ce9db73f92b1..1d552486921e1 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/breakdowns.test.tsx
@@ -9,15 +9,14 @@ import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { Breakdowns } from './breakdowns';
import { mockIndexPattern, render } from '../../rtl_helpers';
-import { NEW_SERIES_KEY } from '../../hooks/use_series_storage';
import { getDefaultConfigs } from '../../configurations/default_configs';
import { USER_AGENT_OS } from '../../configurations/constants/elasticsearch_fieldnames';
describe('Breakdowns', function () {
const dataViewSeries = getDefaultConfigs({
- reportType: 'pld',
+ reportType: 'dist',
indexPattern: mockIndexPattern,
- seriesId: NEW_SERIES_KEY,
+ dataType: 'ux',
});
it('should render properly', async function () {
@@ -53,7 +52,7 @@ describe('Breakdowns', function () {
expect(setSeries).toHaveBeenCalledWith('series-id', {
breakdown: 'user_agent.name',
dataType: 'ux',
- reportType: 'pld',
+ reportType: 'dist',
time: { from: 'now-15m', to: 'now' },
});
expect(setSeries).toHaveBeenCalledTimes(1);
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_options.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_options.tsx
index 975817a8417de..08664ac75eb8d 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_options.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_options.tsx
@@ -13,17 +13,18 @@ import { SeriesChartTypesSelect } from '../../series_builder/columns/chart_types
interface Props {
series: DataSeries;
+ seriesId: string;
}
-export function ChartOptions({ series }: Props) {
+export function ChartOptions({ series, seriesId }: Props) {
return (
-
+
{series.hasOperationType && (
-
+
)}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx
index 5374fc33093a1..086a1d4341bbc 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_actions.tsx
@@ -10,7 +10,6 @@ import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { RemoveSeries } from './remove_series';
import { NEW_SERIES_KEY, useSeriesStorage } from '../../hooks/use_series_storage';
-import { ReportToDataTypeMap } from '../../configurations/constants';
interface Props {
seriesId: string;
@@ -21,7 +20,7 @@ export function SeriesActions({ seriesId }: Props) {
const onEdit = () => {
removeSeries(seriesId);
- setSeries(NEW_SERIES_KEY, { ...series, dataType: ReportToDataTypeMap[series.reportType] });
+ setSeries(NEW_SERIES_KEY, { ...series });
};
return (
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.test.tsx
index 2919714ac0cd4..8363b6b0eadfd 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.test.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/selected_filters.test.tsx
@@ -10,16 +10,15 @@ import { screen, waitFor } from '@testing-library/react';
import { mockAppIndexPattern, mockIndexPattern, render } from '../rtl_helpers';
import { SelectedFilters } from './selected_filters';
import { getDefaultConfigs } from '../configurations/default_configs';
-import { NEW_SERIES_KEY } from '../hooks/use_series_storage';
import { USER_AGENT_NAME } from '../configurations/constants/elasticsearch_fieldnames';
describe('SelectedFilters', function () {
mockAppIndexPattern();
const dataViewSeries = getDefaultConfigs({
- reportType: 'pld',
+ reportType: 'dist',
indexPattern: mockIndexPattern,
- seriesId: NEW_SERIES_KEY,
+ dataType: 'ux',
});
it('should render properly', async function () {
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx
index 6e513fcd2fec9..79218aa111f16 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx
@@ -18,6 +18,11 @@ import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern';
import { SeriesActions } from './columns/series_actions';
import { ChartEditOptions } from './chart_edit_options';
+interface EditItem {
+ seriesConfig: DataSeries;
+ id: string;
+}
+
export function SeriesEditor() {
const { allSeries, firstSeriesId } = useSeriesStorage();
@@ -43,8 +48,8 @@ export function SeriesEditor() {
}),
field: 'defaultFilters',
width: '15%',
- render: (defaultFilters: string[], series: DataSeries) => (
-
+ render: (defaultFilters: string[], { id, seriesConfig }: EditItem) => (
+
),
},
{
@@ -53,8 +58,8 @@ export function SeriesEditor() {
}),
field: 'breakdowns',
width: '25%',
- render: (val: string[], item: DataSeries) => (
-
+ render: (val: string[], item: EditItem) => (
+
),
},
{
@@ -69,7 +74,7 @@ export function SeriesEditor() {
width: '20%',
field: 'id',
align: 'right' as const,
- render: (val: string, item: DataSeries) => ,
+ render: (val: string, item: EditItem) => ,
},
{
name: i18n.translate('xpack.observability.expView.seriesEditor.actions', {
@@ -78,7 +83,7 @@ export function SeriesEditor() {
align: 'center' as const,
width: '10%',
field: 'id',
- render: (val: string, item: DataSeries) => ,
+ render: (val: string, item: EditItem) => ,
},
]
: []),
@@ -86,20 +91,21 @@ export function SeriesEditor() {
const allSeriesKeys = Object.keys(allSeries);
- const items: DataSeries[] = [];
+ const items: EditItem[] = [];
const { indexPattern } = useAppIndexPatternContext();
allSeriesKeys.forEach((seriesKey) => {
const series = allSeries[seriesKey];
if (series.reportType && indexPattern) {
- items.push(
- getDefaultConfigs({
+ items.push({
+ id: seriesKey,
+ seriesConfig: getDefaultConfigs({
indexPattern,
reportType: series.reportType,
- seriesId: seriesKey,
- })
- );
+ dataType: series.dataType,
+ }),
+ });
}
});
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts
index 87772532f410d..98605dfdb4ca3 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts
@@ -20,17 +20,9 @@ import { IIndexPattern } from '../../../../../../../src/plugins/data/common/inde
import { ExistsFilter } from '../../../../../../../src/plugins/data/common/es_query/filters';
export const ReportViewTypes = {
- pld: 'page-load-dist',
- kpi: 'kpi-trends',
+ dist: 'data-distribution',
+ kpi: 'kpi-over-time',
cwv: 'core-web-vitals',
- upd: 'uptime-duration',
- upp: 'uptime-pings',
- svl: 'service-latency',
- tpt: 'service-throughput',
- logs: 'logs-frequency',
- cpu: 'cpu-usage',
- mem: 'memory-usage',
- nwk: 'network-activity',
} as const;
type ValueOf = T[keyof T];
@@ -60,7 +52,6 @@ export interface ReportDefinition {
export interface DataSeries {
reportType: ReportViewType;
- id: string;
xAxisColumn: Partial | Partial;
yAxisColumns: Array>;
@@ -100,7 +91,6 @@ export interface UrlFilter {
}
export interface ConfigProps {
- seriesId: string;
indexPattern: IIndexPattern;
}
diff --git a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx
index c864462c76915..35161561a23fe 100644
--- a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx
+++ b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx
@@ -191,7 +191,7 @@ export const PingHistogramComponent: React.FC = ({
{
'pings-over-time': {
dataType: 'synthetics',
- reportType: 'upp',
+ reportType: 'kpi',
time: { from: dateRangeStart, to: dateRangeEnd },
...(monitorId ? { filters: [{ field: 'monitor.id', values: [monitorId] }] } : {}),
},
diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration_container.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration_container.tsx
index 6cfe8f61a1843..377d7a8fa35d4 100644
--- a/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration_container.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration_container.tsx
@@ -56,7 +56,7 @@ export const MonitorDuration: React.FC = ({ monitorId }) => {
const exploratoryViewLink = createExploratoryViewUrl(
{
[`monitor-duration`]: {
- reportType: 'upd',
+ reportType: 'kpi',
time: { from: dateRangeStart, to: dateRangeEnd },
reportDefinitions: {
'monitor.id': [monitorId] as string[],
From dbe6bfd073f024477263d2adad91d1f98d69b8ea Mon Sep 17 00:00:00 2001
From: Anton Dosov
Date: Mon, 14 Jun 2021 16:39:42 +0200
Subject: [PATCH 03/91] [Index Patterns] Cover field editor with a11y tests
(#101888)
---
test/accessibility/apps/management.ts | 22 ++++++++++++++
test/functional/page_objects/settings_page.ts | 29 ++++++++++++++-----
2 files changed, 44 insertions(+), 7 deletions(-)
diff --git a/test/accessibility/apps/management.ts b/test/accessibility/apps/management.ts
index 692b140ade7ee..e71f6bb3ebfee 100644
--- a/test/accessibility/apps/management.ts
+++ b/test/accessibility/apps/management.ts
@@ -13,6 +13,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const a11y = getService('a11y');
+ const testSubjects = getService('testSubjects');
describe('Management', () => {
before(async () => {
@@ -43,6 +44,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await a11y.testAppSnapshot();
});
+ it('Index pattern field editor - initial view', async () => {
+ await PageObjects.settings.clickAddField();
+ await a11y.testAppSnapshot();
+ });
+
+ it('Index pattern field editor - all options shown', async () => {
+ await PageObjects.settings.setFieldName('test');
+ await PageObjects.settings.setFieldType('Keyword');
+ await PageObjects.settings.setFieldScript("emit('hello world')");
+ await PageObjects.settings.toggleRow('formatRow');
+ await PageObjects.settings.setFieldFormat('string');
+ await PageObjects.settings.toggleRow('customLabelRow');
+ await PageObjects.settings.setCustomLabel('custom label');
+ await testSubjects.click('toggleAdvancedSetting');
+
+ await a11y.testAppSnapshot();
+
+ await testSubjects.click('euiFlyoutCloseButton');
+ await PageObjects.settings.closeIndexPatternFieldEditor();
+ });
+
it('Open create index pattern wizard', async () => {
await PageObjects.settings.clickKibanaIndexPatterns();
await PageObjects.settings.clickAddNewIndexPatternButton();
diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts
index 7d7da79b4a397..88951bb04c956 100644
--- a/test/functional/page_objects/settings_page.ts
+++ b/test/functional/page_objects/settings_page.ts
@@ -563,11 +563,8 @@ export class SettingsPageObject extends FtrService {
async setFieldScript(script: string) {
this.log.debug('set script = ' + script);
- const formatRow = await this.testSubjects.find('valueRow');
- const formatRowToggle = (await formatRow.findAllByCssSelector('[data-test-subj="toggle"]'))[0];
-
- await formatRowToggle.click();
- const getMonacoTextArea = async () => (await formatRow.findAllByCssSelector('textarea'))[0];
+ const valueRow = await this.toggleRow('valueRow');
+ const getMonacoTextArea = async () => (await valueRow.findAllByCssSelector('textarea'))[0];
this.retry.waitFor('monaco editor is ready', async () => !!(await getMonacoTextArea()));
const monacoTextArea = await getMonacoTextArea();
await monacoTextArea.focus();
@@ -576,8 +573,8 @@ export class SettingsPageObject extends FtrService {
async changeFieldScript(script: string) {
this.log.debug('set script = ' + script);
- const formatRow = await this.testSubjects.find('valueRow');
- const getMonacoTextArea = async () => (await formatRow.findAllByCssSelector('textarea'))[0];
+ const valueRow = await this.testSubjects.find('valueRow');
+ const getMonacoTextArea = async () => (await valueRow.findAllByCssSelector('textarea'))[0];
this.retry.waitFor('monaco editor is ready', async () => !!(await getMonacoTextArea()));
const monacoTextArea = await getMonacoTextArea();
await monacoTextArea.focus();
@@ -622,6 +619,24 @@ export class SettingsPageObject extends FtrService {
);
}
+ async toggleRow(rowTestSubj: string) {
+ this.log.debug('toggling tow = ' + rowTestSubj);
+ const row = await this.testSubjects.find(rowTestSubj);
+ const rowToggle = (await row.findAllByCssSelector('[data-test-subj="toggle"]'))[0];
+ await rowToggle.click();
+ return row;
+ }
+
+ async setCustomLabel(label: string) {
+ this.log.debug('set custom label = ' + label);
+ await (
+ await this.testSubjects.findDescendant(
+ 'input',
+ await this.testSubjects.find('customLabelRow')
+ )
+ ).type(label);
+ }
+
async setScriptedFieldUrlType(type: string) {
this.log.debug('set scripted field Url type = ' + type);
await this.find.clickByCssSelector(
From b430a985c2e392201cc4afd1bdb14e111320231e Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 14 Jun 2021 07:43:24 -0700
Subject: [PATCH 04/91] Update dependency @elastic/charts to v30.1.0 (master)
(#101683)
---
package.json | 2 +-
.../common/charts/__snapshots__/donut_chart.test.tsx.snap | 2 +-
yarn.lock | 8 ++++----
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/package.json b/package.json
index b4f9109503261..513352db3f81b 100644
--- a/package.json
+++ b/package.json
@@ -99,7 +99,7 @@
"dependencies": {
"@elastic/apm-rum": "^5.6.1",
"@elastic/apm-rum-react": "^1.2.5",
- "@elastic/charts": "30.0.0",
+ "@elastic/charts": "30.1.0",
"@elastic/datemath": "link:bazel-bin/packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.13",
"@elastic/ems-client": "7.13.0",
diff --git a/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap
index b689ca7ff56f0..1403e6c3e52b1 100644
--- a/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap
@@ -137,7 +137,7 @@ exports[`DonutChart component passes correct props without errors for valid prop
"opacity": 1,
},
"rectBorder": Object {
- "strokeWidth": 0,
+ "strokeWidth": 1,
"visible": false,
},
},
diff --git a/yarn.lock b/yarn.lock
index 9543f209d6849..0e2b953389c10 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1359,10 +1359,10 @@
dependencies:
object-hash "^1.3.0"
-"@elastic/charts@30.0.0":
- version "30.0.0"
- resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-30.0.0.tgz#e19ad8b94928aa9bac5d7facc488fa69b683ff1e"
- integrity sha512-r22T2dlW3drEmrIx6JNlOOzSp0JCkI/qbIfmvzdMBu8E8hITkJTaXJaLsNN4mz9EvR9jEM8XQQPQXOFKJhWixw==
+"@elastic/charts@30.1.0":
+ version "30.1.0"
+ resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-30.1.0.tgz#eb9b3348c149ce13f74876738a9d2899b6b10067"
+ integrity sha512-aUfXRQYQopm+6O48tEO0v/w6fETYORGiSPBRtqlq5xPncZGhGnQbgXVNQsPngYqapnKpOupXAqzjopF+RJ4QWg==
dependencies:
"@popperjs/core" "^2.4.0"
chroma-js "^2.1.0"
From 8424a925332d5bd7d93148fbab72d1f518933485 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Mon, 14 Jun 2021 10:50:20 -0400
Subject: [PATCH 05/91] [ILM] Migrate to new page layout (#101927)
---
.../__snapshots__/policy_table.test.tsx.snap | 98 +++--
.../edit_policy/edit_policy.container.tsx | 70 ++--
.../sections/edit_policy/edit_policy.tsx | 351 +++++++++---------
.../policy_table/policy_table.container.tsx | 70 ++--
.../sections/policy_table/policy_table.tsx | 101 +++--
5 files changed, 339 insertions(+), 351 deletions(-)
diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap
index 8d839e196916b..556ac35d0565e 100644
--- a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap
+++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap
@@ -46,71 +46,67 @@ Array [
`;
exports[`policy table should show empty state when there are not any policies 1`] = `
-
+
+
-
-
-
-
- Create your first index lifecycle policy
-
-
-
-
- An index lifecycle policy helps you manage your indices as they age.
-
-
-
+ Create your first index lifecycle policy
+
-
+
+ An index lifecycle policy helps you manage your indices as they age.
+
+
+
+
+
+
+
+
-
-
- Create policy
-
+ Create policy
-
-
+
+
-
+
`;
exports[`policy table should sort when linked indices header is clicked 1`] = `
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx
index fe82b20093072..07c2228863b81 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.tsx
@@ -7,7 +7,7 @@
import React, { useEffect } from 'react';
import { RouteComponentProps } from 'react-router-dom';
-import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui';
+import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner, EuiPageContent } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { MIN_SEARCHABLE_SNAPSHOT_LICENSE } from '../../../../common/constants';
@@ -52,43 +52,47 @@ export const EditPolicy: React.FunctionComponent }
- body={
-
- }
- />
+
+ }
+ body={
+
+ }
+ />
+
);
}
if (error || !policies) {
const { statusCode, message } = error ? error : { statusCode: '', message: '' };
return (
-
-
-
- }
- body={
-
- {message} ({statusCode})
-
- }
- actions={
-
-
-
- }
- />
+
+
+
+
+ }
+ body={
+
+ {message} ({statusCode})
+
+ }
+ actions={
+
+
+
+ }
+ />
+
);
}
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx
index d7368249d76e8..172e8259b87af 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx
@@ -19,15 +19,10 @@ import {
EuiFlexItem,
EuiFormRow,
EuiHorizontalRule,
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiPageContentHeader,
- EuiPageContentHeaderSection,
EuiSpacer,
EuiSwitch,
EuiText,
- EuiTitle,
+ EuiPageHeader,
} from '@elastic/eui';
import { TextField, useForm, useFormData, useKibana } from '../../../shared_imports';
@@ -153,201 +148,199 @@ export const EditPolicy: React.FunctionComponent = ({ history }) => {
};
return (
-
-
-
-
-
-
-
- {isNewPolicy
- ? i18n.translate('xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage', {
- defaultMessage: 'Create policy',
- })
- : i18n.translate('xpack.indexLifecycleMgmt.editPolicy.editPolicyMessage', {
- defaultMessage: 'Edit policy {originalPolicyName}',
- values: { originalPolicyName },
- })}
-
-
-
-
-
+ <>
+
+ {isNewPolicy
+ ? i18n.translate('xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage', {
+ defaultMessage: 'Create policy',
+ })
+ : i18n.translate('xpack.indexLifecycleMgmt.editPolicy.editPolicyMessage', {
+ defaultMessage: 'Edit policy {originalPolicyName}',
+ values: { originalPolicyName },
+ })}
+
+ }
+ bottomBorder
+ rightSideItems={[
+
+
+ ,
+ ]}
+ />
+
+
+
+
-
-
-
+
+
+
+
+ {
+ setSaveAsNew(e.target.checked);
}}
+ label={
+
+
+
+ }
/>
- ) : null}
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ )}
+
+ {saveAsNew || isNewPolicy ? (
+
+ path={policyNamePath}
+ config={{
+ label: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.policyNameLabel', {
+ defaultMessage: 'Policy name',
+ }),
+ helpText: i18n.translate(
+ 'xpack.indexLifecycleMgmt.editPolicy.validPolicyNameMessage',
+ {
+ defaultMessage:
+ 'A policy name cannot start with an underscore and cannot contain a comma or a space.',
+ }
+ ),
+ validations: policyNameValidations,
+ }}
+ component={TextField}
+ componentProps={{
+ fullWidth: false,
+ euiFieldProps: {
+ 'data-test-subj': 'policyNameField',
+ },
+ }}
+ />
+ ) : null}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isAllowedByLicense && (
+ <>
-
+
+ >
+ )}
- {isAllowedByLicense && (
- <>
-
-
- >
- )}
-
- {/* We can't add the here as it breaks the layout
+ {/* We can't add the here as it breaks the layout
and makes the connecting line go further that it needs to.
There is an issue in EUI to fix this (https://github.com/elastic/eui/issues/4492) */}
-
-
+
+
-
+
-
-
-
-
-
-
-
- {saveAsNew ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
- {isShowingPolicyJsonFlyout ? (
+
+ {saveAsNew ? (
) : (
)}
+
+
+
+
+
+
+
- {isShowingPolicyJsonFlyout ? (
- setIsShowingPolicyJsonFlyout(false)}
- />
- ) : null}
-
-
-
-
+
+
+ {isShowingPolicyJsonFlyout ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {isShowingPolicyJsonFlyout ? (
+ setIsShowingPolicyJsonFlyout(false)}
+ />
+ ) : null}
+
+ >
);
};
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx
index 7cf50de0ee999..deac2bb239d30 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.container.tsx
@@ -8,7 +8,7 @@
import React, { useEffect } from 'react';
import { ApplicationStart } from 'kibana/public';
import { RouteComponentProps } from 'react-router-dom';
-import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui';
+import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner, EuiPageContent } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { PolicyTable as PresentationComponent } from './policy_table';
import { useKibana } from '../../../shared_imports';
@@ -33,43 +33,47 @@ export const PolicyTable: React.FunctionComponent =
if (isLoading) {
return (
- }
- body={
-
- }
- />
+
+ }
+ body={
+
+ }
+ />
+
);
}
if (error) {
const { statusCode, message } = error ? error : { statusCode: '', message: '' };
return (
-
-
-
- }
- body={
-
- {message} ({statusCode})
-
- }
- actions={
-
-
-
- }
- />
+
+
+
+
+ }
+ body={
+
+ {message} ({statusCode})
+
+ }
+ actions={
+
+
+
+ }
+ />
+
);
}
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx
index 3b0f815800ba3..ba89d6c895d93 100644
--- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx
+++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx
@@ -16,9 +16,7 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
- EuiTitle,
- EuiText,
- EuiPageBody,
+ EuiPageHeader,
EuiPageContent,
} from '@elastic/eui';
import { ApplicationStart } from 'kibana/public';
@@ -119,67 +117,60 @@ export const PolicyTable: React.FunctionComponent = ({
);
} else {
return (
-
-
-
+
+
+
+
+ }
+ body={
+
+
-
- }
- body={
-
-
-
-
-
- }
- actions={createPolicyButton}
- />
-
-
+
+
+ }
+ actions={createPolicyButton}
+ />
+
);
}
return (
-
-
- {confirmModal}
+ <>
+ {confirmModal}
-
-
-
-
-
-
-
-
- {createPolicyButton}
-
-
-
-
+
-
-
+
+ }
+ description={
+
+ }
+ bottomBorder
+ rightSideItems={[createPolicyButton]}
+ />
-
- {content}
-
-
+
+
+ {content}
+ >
);
};
From 277212df0b919b57d3dadd40bf059eb908b44fd0 Mon Sep 17 00:00:00 2001
From: Stacey Gammon
Date: Mon, 14 Jun 2021 11:04:27 -0400
Subject: [PATCH 06/91] Update building_a_plugin.mdx (#101921)
* Update building_a_plugin.mdx
* put back the numbers on the same line
* Add info about requiredBundles
* Fix numbers again
* Update dev_docs/tutorials/building_a_plugin.mdx
Co-authored-by: Mikhail Shustov
* Update dev_docs/tutorials/building_a_plugin.mdx
Co-authored-by: Mikhail Shustov
Co-authored-by: Mikhail Shustov
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
dev_docs/tutorials/building_a_plugin.mdx | 79 ++++++++++++++++++------
1 file changed, 59 insertions(+), 20 deletions(-)
diff --git a/dev_docs/tutorials/building_a_plugin.mdx b/dev_docs/tutorials/building_a_plugin.mdx
index cee5a9a399de5..e751ce7d01b16 100644
--- a/dev_docs/tutorials/building_a_plugin.mdx
+++ b/dev_docs/tutorials/building_a_plugin.mdx
@@ -4,7 +4,7 @@ slug: /kibana-dev-docs/tutorials/build-a-plugin
title: Kibana plugin tutorial
summary: Anatomy of a Kibana plugin and how to build one
date: 2021-02-05
-tags: ['kibana','onboarding', 'dev', 'tutorials']
+tags: ['kibana', 'onboarding', 'dev', 'tutorials']
---
Prereading material:
@@ -14,7 +14,7 @@ Prereading material:
## The anatomy of a plugin
Plugins are defined as classes and present themselves to Kibana through a simple wrapper function. A plugin can have browser-side code, server-side code,
-or both. There is no architectural difference between a plugin in the browser and a plugin on the server. In both places, you describe your plugin similarly,
+or both. There is no architectural difference between a plugin in the browser and a plugin on the server. In both places, you describe your plugin similarly,
and you interact with Core and other plugins in the same way.
The basic file structure of a Kibana plugin named demo that has both client-side and server-side code would be:
@@ -33,7 +33,7 @@ plugins/
index.ts [6]
```
-### [1] kibana.json
+### [1] kibana.json
`kibana.json` is a static manifest file that is used to identify the plugin and to specify if this plugin has server-side code, browser-side code, or both:
@@ -42,14 +42,33 @@ plugins/
"id": "demo",
"version": "kibana",
"server": true,
- "ui": true
+ "ui": true,
+ "owner": { [1]
+ "name": "App Services",
+ "githubTeam": "kibana-app-services"
+ },
+ "description": "This plugin extends Kibana by doing xyz, and allows other plugins to extend Kibana by offering abc functionality. It also exposes some helper utilities that do efg", [2]
+ "requiredPlugins": ["data"], [3]
+ "optionalPlugins": ["alerting"] [4]
+ "requiredBundles": ["anotherPlugin"] [5]
}
```
+[1], [2]: Every internal plugin should fill in the owner and description properties.
+
+[3], [4]: Any plugin that you have a dependency on should be listed in `requiredPlugins` or `optionalPlugins`. Doing this will ensure that you have access to that plugin's start and setup contract inside your own plugin's start and setup lifecycle methods. If a plugin you optionally depend on is not installed or disabled, it will be undefined if you try to access it. If a plugin you require is not installed or disabled, kibana will fail to build.
+
+[5]: Don't worry too much about getting 5 right. The build optimizer will complain if any of these values are incorrect.
+
+
+
+ You don't need to declare a dependency on a plugin if you only wish to access its types.
+
+
### [2] public/index.ts
-`public/index.ts` is the entry point into the client-side code of this plugin. It must export a function named plugin, which will receive a standard set of
- core capabilities as an argument. It should return an instance of its plugin class for Kibana to load.
+`public/index.ts` is the entry point into the client-side code of this plugin. Everything exported from this file will be a part of the plugins . If the plugin only exists to export static utilities, consider using a package. Otherwise, this file must export a function named plugin, which will receive a standard set of
+core capabilities as an argument. It should return an instance of its plugin class for Kibana to load.
```
import type { PluginInitializerContext } from 'kibana/server';
@@ -60,13 +79,32 @@ export function plugin(initializerContext: PluginInitializerContext) {
}
```
+
+
+1. When possible, use
+
+```
+export type { AType } from '...'`
+```
+
+instead of
+
+```
+export { AType } from '...'`.
+```
+
+Using the non-`type` variation will increase the bundle size unnecessarily and may unwillingly provide access to the implementation of `AType` class.
+
+2. Don't use `export *` in these top level index.ts files
+
+
+
### [3] public/plugin.ts
`public/plugin.ts` is the client-side plugin definition itself. Technically speaking, it does not need to be a class or even a separate file from the entry
- point, but all plugins at Elastic should be consistent in this way.
+point, but all plugins at Elastic should be consistent in this way.
-
- ```ts
+```ts
import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server';
export class DemoPlugin implements Plugin {
@@ -84,10 +122,9 @@ export class DemoPlugin implements Plugin {
// called when plugin is torn down during Kibana's shutdown sequence
}
}
- ```
-
+```
-### [4] server/index.ts
+### [4] server/index.ts
`server/index.ts` is the entry-point into the server-side code of this plugin. It is identical in almost every way to the client-side entry-point:
@@ -115,7 +152,7 @@ export class DemoPlugin implements Plugin {
}
```
-Kibana does not impose any technical restrictions on how the the internals of a plugin are architected, though there are certain
+Kibana does not impose any technical restrictions on how the the internals of a plugin are architected, though there are certain
considerations related to how plugins integrate with core APIs and APIs exposed by other plugins that may greatly impact how they are built.
### [6] common/index.ts
@@ -124,8 +161,8 @@ considerations related to how plugins integrate with core APIs and APIs exposed
## How plugin's interact with each other, and Core
-The lifecycle-specific contracts exposed by core services are always passed as the first argument to the equivalent lifecycle function in a plugin.
-For example, the core http service exposes a function createRouter to all plugin setup functions. To use this function to register an HTTP route handler,
+The lifecycle-specific contracts exposed by core services are always passed as the first argument to the equivalent lifecycle function in a plugin.
+For example, the core http service exposes a function createRouter to all plugin setup functions. To use this function to register an HTTP route handler,
a plugin just accesses it off of the first argument:
```ts
@@ -135,14 +172,16 @@ export class DemoPlugin {
public setup(core: CoreSetup) {
const router = core.http.createRouter();
// handler is called when '/path' resource is requested with `GET` method
- router.get({ path: '/path', validate: false }, (context, req, res) => res.ok({ content: 'ok' }));
+ router.get({ path: '/path', validate: false }, (context, req, res) =>
+ res.ok({ content: 'ok' })
+ );
}
}
```
Unlike core, capabilities exposed by plugins are not automatically injected into all plugins.
Instead, if a plugin wishes to use the public interface provided by another plugin, it must first declare that plugin as a
- dependency in it’s kibana.json manifest file.
+dependency in it’s kibana.json manifest file.
** foobar plugin.ts: **
@@ -174,8 +213,8 @@ export class MyPlugin implements Plugin {
}
}
```
-[1] We highly encourage plugin authors to explicitly declare public interfaces for their plugins.
+[1] We highly encourage plugin authors to explicitly declare public interfaces for their plugins.
** demo kibana.json**
@@ -194,7 +233,7 @@ With that specified in the plugin manifest, the appropriate interfaces are then
import type { CoreSetup, CoreStart } from 'kibana/server';
import type { FoobarPluginSetup, FoobarPluginStart } from '../../foobar/server';
-interface DemoSetupPlugins { [1]
+interface DemoSetupPlugins { [1]
foobar: FoobarPluginSetup;
}
@@ -218,7 +257,7 @@ export class DemoPlugin {
public stop() {}
}
```
-
+
[1] The interface for plugin’s dependencies must be manually composed. You can do this by importing the appropriate type from the plugin and constructing an interface where the property name is the plugin’s ID.
[2] These manually constructed types should then be used to specify the type of the second argument to the plugin.
From 0e7d4fed93b98d4b746f30141535a852c8b42f69 Mon Sep 17 00:00:00 2001
From: Josh Dover <1813008+joshdover@users.noreply.github.com>
Date: Mon, 14 Jun 2021 17:14:02 +0200
Subject: [PATCH 07/91] Fix delayed status API updates in alerting and
task_manager (#101778)
---
test/functional/apps/bundles/index.js | 9 ++++++
.../server_integration/http/platform/cache.ts | 11 +++++++
.../alerting/server/health/get_state.test.ts | 20 +++++-------
.../alerting/server/health/get_state.ts | 13 +++++++-
x-pack/plugins/alerting/server/plugin.ts | 31 ++++++++++++-------
x-pack/plugins/task_manager/server/plugin.ts | 14 ++++-----
6 files changed, 65 insertions(+), 33 deletions(-)
diff --git a/test/functional/apps/bundles/index.js b/test/functional/apps/bundles/index.js
index d13e74dd4eed9..577035a8c343c 100644
--- a/test/functional/apps/bundles/index.js
+++ b/test/functional/apps/bundles/index.js
@@ -18,6 +18,15 @@ export default function ({ getService }) {
let buildNum;
before(async () => {
+ // Wait for status to become green
+ let status;
+ const start = Date.now();
+ do {
+ const resp = await supertest.get('/api/status');
+ status = resp.status;
+ // Stop polling once status stabilizes OR once 40s has passed
+ } while (status !== 200 && Date.now() - start < 40_000);
+
const resp = await supertest.get('/api/status').expect(200);
buildNum = resp.body.version.build_number;
});
diff --git a/test/server_integration/http/platform/cache.ts b/test/server_integration/http/platform/cache.ts
index 2c1aa90e963e2..a33f916cf1c4b 100644
--- a/test/server_integration/http/platform/cache.ts
+++ b/test/server_integration/http/platform/cache.ts
@@ -12,6 +12,17 @@ export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
describe('kibana server cache-control', () => {
+ before(async () => {
+ // Wait for status to become green
+ let status;
+ const start = Date.now();
+ do {
+ const resp = await supertest.get('/api/status');
+ status = resp.status;
+ // Stop polling once status stabilizes OR once 40s has passed
+ } while (status !== 200 && Date.now() - start < 40_000);
+ });
+
it('properly marks responses as private, with directives to disable caching', async () => {
await supertest
.get('/api/status')
diff --git a/x-pack/plugins/alerting/server/health/get_state.test.ts b/x-pack/plugins/alerting/server/health/get_state.test.ts
index 96627e10fb3bd..2dddf81e3b766 100644
--- a/x-pack/plugins/alerting/server/health/get_state.test.ts
+++ b/x-pack/plugins/alerting/server/health/get_state.test.ts
@@ -58,7 +58,6 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => {
const mockTaskManager = taskManagerMock.createStart();
mockTaskManager.get.mockResolvedValue(getHealthCheckTask());
const pollInterval = 100;
- const halfInterval = Math.floor(pollInterval / 2);
getHealthStatusStream(
mockTaskManager,
@@ -77,16 +76,15 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => {
pollInterval
).subscribe();
- // shouldn't fire before poll interval passes
+ // should fire before poll interval passes
// should fire once each poll interval
- jest.advanceTimersByTime(halfInterval);
- expect(mockTaskManager.get).toHaveBeenCalledTimes(0);
- jest.advanceTimersByTime(halfInterval);
expect(mockTaskManager.get).toHaveBeenCalledTimes(1);
jest.advanceTimersByTime(pollInterval);
expect(mockTaskManager.get).toHaveBeenCalledTimes(2);
jest.advanceTimersByTime(pollInterval);
expect(mockTaskManager.get).toHaveBeenCalledTimes(3);
+ jest.advanceTimersByTime(pollInterval);
+ expect(mockTaskManager.get).toHaveBeenCalledTimes(4);
});
it('should retry on error', async () => {
@@ -94,7 +92,6 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => {
mockTaskManager.get.mockRejectedValue(new Error('Failure'));
const retryDelay = 10;
const pollInterval = 100;
- const halfInterval = Math.floor(pollInterval / 2);
getHealthStatusStream(
mockTaskManager,
@@ -114,28 +111,27 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => {
retryDelay
).subscribe();
- jest.advanceTimersByTime(halfInterval);
- expect(mockTaskManager.get).toHaveBeenCalledTimes(0);
- jest.advanceTimersByTime(halfInterval);
expect(mockTaskManager.get).toHaveBeenCalledTimes(1);
+ jest.advanceTimersByTime(pollInterval);
+ expect(mockTaskManager.get).toHaveBeenCalledTimes(2);
// Retry on failure
let numTimesCalled = 1;
for (let i = 0; i < MAX_RETRY_ATTEMPTS; i++) {
await tick();
jest.advanceTimersByTime(retryDelay);
- expect(mockTaskManager.get).toHaveBeenCalledTimes(numTimesCalled++ + 1);
+ expect(mockTaskManager.get).toHaveBeenCalledTimes(numTimesCalled++ + 2);
}
// Once we've exceeded max retries, should not try again
await tick();
jest.advanceTimersByTime(retryDelay);
- expect(mockTaskManager.get).toHaveBeenCalledTimes(numTimesCalled);
+ expect(mockTaskManager.get).toHaveBeenCalledTimes(numTimesCalled + 1);
// Once another poll interval passes, should call fn again
await tick();
jest.advanceTimersByTime(pollInterval - MAX_RETRY_ATTEMPTS * retryDelay);
- expect(mockTaskManager.get).toHaveBeenCalledTimes(numTimesCalled + 1);
+ expect(mockTaskManager.get).toHaveBeenCalledTimes(numTimesCalled + 2);
});
it('should return healthy status when health status is "ok"', async () => {
diff --git a/x-pack/plugins/alerting/server/health/get_state.ts b/x-pack/plugins/alerting/server/health/get_state.ts
index 30099614ea42b..255037d7015a2 100644
--- a/x-pack/plugins/alerting/server/health/get_state.ts
+++ b/x-pack/plugins/alerting/server/health/get_state.ts
@@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
import { defer, of, interval, Observable, throwError, timer } from 'rxjs';
-import { catchError, mergeMap, retryWhen, switchMap } from 'rxjs/operators';
+import { catchError, mergeMap, retryWhen, startWith, switchMap } from 'rxjs/operators';
import {
Logger,
SavedObjectsServiceStart,
@@ -121,6 +121,17 @@ export const getHealthStatusStream = (
retryDelay?: number
): Observable> =>
interval(healthStatusInterval ?? HEALTH_STATUS_INTERVAL).pipe(
+ // Emit an initial check
+ startWith(
+ getHealthServiceStatusWithRetryAndErrorHandling(
+ taskManager,
+ logger,
+ savedObjects,
+ config,
+ retryDelay
+ )
+ ),
+ // On each interval do a new check
switchMap(() =>
getHealthServiceStatusWithRetryAndErrorHandling(
taskManager,
diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts
index 769243b8feaf6..41ae9f15d9af9 100644
--- a/x-pack/plugins/alerting/server/plugin.ts
+++ b/x-pack/plugins/alerting/server/plugin.ts
@@ -7,7 +7,7 @@
import type { PublicMethodsOf } from '@kbn/utility-types';
import { first, map, share } from 'rxjs/operators';
-import { Observable } from 'rxjs';
+import { BehaviorSubject, Observable } from 'rxjs';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { combineLatest } from 'rxjs';
import { SecurityPluginSetup, SecurityPluginStart } from '../../security/server';
@@ -34,6 +34,7 @@ import {
StatusServiceSetup,
ServiceStatus,
SavedObjectsBulkGetObject,
+ ServiceStatusLevels,
} from '../../../../src/core/server';
import type { AlertingRequestHandlerContext } from './types';
import { defineRoutes } from './routes';
@@ -226,17 +227,23 @@ export class AlertingPlugin {
this.config
);
+ const serviceStatus$ = new BehaviorSubject({
+ level: ServiceStatusLevels.unavailable,
+ summary: 'Alerting is initializing',
+ });
+ core.status.set(serviceStatus$);
+
core.getStartServices().then(async ([coreStart, startPlugins]) => {
- core.status.set(
- combineLatest([
- core.status.derivedStatus$,
- getHealthStatusStream(
- startPlugins.taskManager,
- this.logger,
- coreStart.savedObjects,
- this.config
- ),
- ]).pipe(
+ combineLatest([
+ core.status.derivedStatus$,
+ getHealthStatusStream(
+ startPlugins.taskManager,
+ this.logger,
+ coreStart.savedObjects,
+ this.config
+ ),
+ ])
+ .pipe(
map(([derivedStatus, healthStatus]) => {
if (healthStatus.level > derivedStatus.level) {
return healthStatus as ServiceStatus;
@@ -246,7 +253,7 @@ export class AlertingPlugin {
}),
share()
)
- );
+ .subscribe(serviceStatus$);
});
initializeAlertingHealth(this.logger, plugins.taskManager, core.getStartServices());
diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts
index 51199da26ee7d..d3e251b751ef8 100644
--- a/x-pack/plugins/task_manager/server/plugin.ts
+++ b/x-pack/plugins/task_manager/server/plugin.ts
@@ -87,15 +87,13 @@ export class TaskManagerPlugin
this.config!
);
- core.getStartServices().then(async () => {
- core.status.set(
- combineLatest([core.status.derivedStatus$, serviceStatus$]).pipe(
- map(([derivedStatus, serviceStatus]) =>
- serviceStatus.level > derivedStatus.level ? serviceStatus : derivedStatus
- )
+ core.status.set(
+ combineLatest([core.status.derivedStatus$, serviceStatus$]).pipe(
+ map(([derivedStatus, serviceStatus]) =>
+ serviceStatus.level > derivedStatus.level ? serviceStatus : derivedStatus
)
- );
- });
+ )
+ );
return {
index: this.config.index,
From 970e9a037bcb098ec84d9a41c5b1e680ecc05f59 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?=
Date: Mon, 14 Jun 2021 17:14:15 +0200
Subject: [PATCH 08/91] [APM] Add AWS and Azure icons for additional services
(#101901)
---
.../shared/span_icon/get_span_icon.ts | 17 ++++-
.../shared/span_icon/span_icon.stories.tsx | 73 +++++++++++++++++++
2 files changed, 87 insertions(+), 3 deletions(-)
create mode 100644 x-pack/plugins/apm/public/components/shared/span_icon/span_icon.stories.tsx
diff --git a/x-pack/plugins/apm/public/components/shared/span_icon/get_span_icon.ts b/x-pack/plugins/apm/public/components/shared/span_icon/get_span_icon.ts
index bebfcba1a93b4..e2e1391a2f842 100644
--- a/x-pack/plugins/apm/public/components/shared/span_icon/get_span_icon.ts
+++ b/x-pack/plugins/apm/public/components/shared/span_icon/get_span_icon.ts
@@ -6,7 +6,6 @@
*/
import { maybe } from '../../../../common/utils/maybe';
-import awsIcon from './icons/aws.svg';
import cassandraIcon from './icons/cassandra.svg';
import databaseIcon from './icons/database.svg';
import defaultIcon from './icons/default.svg';
@@ -33,12 +32,14 @@ const defaultTypeIcons: { [key: string]: string } = {
resource: globeIcon,
};
-const typeIcons: { [key: string]: { [key: string]: string } } = {
+export const typeIcons: { [type: string]: { [subType: string]: string } } = {
aws: {
- servicename: awsIcon,
+ servicename: 'logoAWS',
},
db: {
cassandra: cassandraIcon,
+ cosmosdb: 'logoAzure',
+ dynamodb: 'logoAWS',
elasticsearch: elasticsearchIcon,
mongodb: mongodbIcon,
mysql: mysqlIcon,
@@ -51,8 +52,18 @@ const typeIcons: { [key: string]: { [key: string]: string } } = {
websocket: websocketIcon,
},
messaging: {
+ azurequeue: 'logoAzure',
+ azureservicebus: 'logoAzure',
jms: javaIcon,
kafka: kafkaIcon,
+ sns: 'logoAWS',
+ sqs: 'logoAWS',
+ },
+ storage: {
+ azureblob: 'logoAzure',
+ azurefile: 'logoAzure',
+ azuretable: 'logoAzure',
+ s3: 'logoAWS',
},
template: {
handlebars: handlebarsIcon,
diff --git a/x-pack/plugins/apm/public/components/shared/span_icon/span_icon.stories.tsx b/x-pack/plugins/apm/public/components/shared/span_icon/span_icon.stories.tsx
new file mode 100644
index 0000000000000..951d3e61f1846
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/shared/span_icon/span_icon.stories.tsx
@@ -0,0 +1,73 @@
+/*
+ * 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 {
+ EuiFlexGrid,
+ EuiFlexItem,
+ EuiCopy,
+ EuiPanel,
+ EuiSpacer,
+ EuiCodeBlock,
+} from '@elastic/eui';
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common';
+import { SpanIcon } from './index';
+import { typeIcons } from './get_span_icon';
+
+const types = Object.keys(typeIcons);
+
+storiesOf('shared/span_icon/span_icon', module)
+ .addDecorator((storyFn) => {storyFn()} )
+ .add(
+ 'Span icon',
+ () => {
+ return (
+ <>
+
+ {' '}
+
+
+
+
+ {types.map((type) => {
+ const subTypes = Object.keys(typeIcons[type]);
+ return (
+ <>
+ {subTypes.map((subType) => {
+ const id = `${type}.${subType}`;
+ return (
+
+
+ {(copy) => (
+
+ {' '}
+ {id}
+
+ )}
+
+
+ );
+ })}
+ >
+ );
+ })}
+
+ >
+ );
+ },
+ {}
+ );
From adda72edd28abf21b201477aaddc55b51e197596 Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Mon, 14 Jun 2021 12:23:28 -0400
Subject: [PATCH 09/91] Document platform security plugins in kibana.json
(#101965)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
src/plugins/security_oss/kibana.json | 5 +++++
src/plugins/spaces_oss/kibana.json | 5 +++++
x-pack/plugins/encrypted_saved_objects/kibana.json | 5 +++++
x-pack/plugins/security/kibana.json | 5 +++++
x-pack/plugins/spaces/kibana.json | 5 +++++
5 files changed, 25 insertions(+)
diff --git a/src/plugins/security_oss/kibana.json b/src/plugins/security_oss/kibana.json
index 70e37d586f1db..c93b5c3b60714 100644
--- a/src/plugins/security_oss/kibana.json
+++ b/src/plugins/security_oss/kibana.json
@@ -1,5 +1,10 @@
{
"id": "securityOss",
+ "owner": {
+ "name": "Platform Security",
+ "githubTeam": "kibana-security"
+ },
+ "description": "This plugin exposes a limited set of security functionality to OSS plugins.",
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": ["security"],
diff --git a/src/plugins/spaces_oss/kibana.json b/src/plugins/spaces_oss/kibana.json
index e048fb7ffb79c..10127634618f1 100644
--- a/src/plugins/spaces_oss/kibana.json
+++ b/src/plugins/spaces_oss/kibana.json
@@ -1,5 +1,10 @@
{
"id": "spacesOss",
+ "owner": {
+ "name": "Platform Security",
+ "githubTeam": "kibana-security"
+ },
+ "description": "This plugin exposes a limited set of spaces functionality to OSS plugins.",
"version": "kibana",
"server": false,
"ui": true,
diff --git a/x-pack/plugins/encrypted_saved_objects/kibana.json b/x-pack/plugins/encrypted_saved_objects/kibana.json
index 74f797ba36a11..4812afe8c2072 100644
--- a/x-pack/plugins/encrypted_saved_objects/kibana.json
+++ b/x-pack/plugins/encrypted_saved_objects/kibana.json
@@ -1,5 +1,10 @@
{
"id": "encryptedSavedObjects",
+ "owner": {
+ "name": "Platform Security",
+ "githubTeam": "kibana-security"
+ },
+ "description": "This plugin provides encryption and decryption utilities for saved objects containing sensitive information.",
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "encryptedSavedObjects"],
diff --git a/x-pack/plugins/security/kibana.json b/x-pack/plugins/security/kibana.json
index f6e7b8bf46a39..a29c01b0f31cc 100644
--- a/x-pack/plugins/security/kibana.json
+++ b/x-pack/plugins/security/kibana.json
@@ -1,5 +1,10 @@
{
"id": "security",
+ "owner": {
+ "name": "Platform Security",
+ "githubTeam": "kibana-security"
+ },
+ "description": "This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user.",
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "security"],
diff --git a/x-pack/plugins/spaces/kibana.json b/x-pack/plugins/spaces/kibana.json
index e67931d4a3b8d..673055b24e8b6 100644
--- a/x-pack/plugins/spaces/kibana.json
+++ b/x-pack/plugins/spaces/kibana.json
@@ -1,5 +1,10 @@
{
"id": "spaces",
+ "owner": {
+ "name": "Platform Security",
+ "githubTeam": "kibana-security"
+ },
+ "description": "This plugin provides the Spaces feature, which allows saved objects to be organized into meaningful categories.",
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "spaces"],
From 91b804f505da9614dc6b85cd64ae64f6d06bea0f Mon Sep 17 00:00:00 2001
From: Nicolas Chaulet
Date: Mon, 14 Jun 2021 12:33:46 -0400
Subject: [PATCH 10/91] [Fleet] Display NOTICE.txt from package if it exists
(#101663)
---
.../plugins/fleet/common/types/models/epm.ts | 8 +-
.../applications/integrations/constants.tsx | 6 +-
.../integrations/sections/epm/constants.tsx | 7 +-
.../epm/screens/detail/overview/details.tsx | 57 +++++++++----
.../screens/detail/overview/notice_modal.tsx | 79 +++++++++++++++++++
.../server/services/epm/archive/index.ts | 7 ++
.../fleet/server/services/epm/packages/get.ts | 1 +
.../server/services/epm/registry/index.ts | 12 +++
8 files changed, 155 insertions(+), 22 deletions(-)
create mode 100644 x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx
diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts
index 0ef9f8b7ace36..f19684b0445e2 100644
--- a/x-pack/plugins/fleet/common/types/models/epm.ts
+++ b/x-pack/plugins/fleet/common/types/models/epm.ts
@@ -48,7 +48,12 @@ export type EpmPackageInstallStatus = 'installed' | 'installing';
export type DetailViewPanelName = 'overview' | 'policies' | 'settings' | 'custom';
export type ServiceName = 'kibana' | 'elasticsearch';
export type AgentAssetType = typeof agentAssetTypes;
-export type AssetType = KibanaAssetType | ElasticsearchAssetType | ValueOf;
+export type DocAssetType = 'doc' | 'notice';
+export type AssetType =
+ | KibanaAssetType
+ | ElasticsearchAssetType
+ | ValueOf
+ | DocAssetType;
/*
Enum mapping of a saved object asset type to how it would appear in a package file path (snake cased)
@@ -344,6 +349,7 @@ export interface EpmPackageAdditions {
latestVersion: string;
assets: AssetsGroupedByServiceByType;
removable?: boolean;
+ notice?: string;
}
type Merge = Omit> &
diff --git a/x-pack/plugins/fleet/public/applications/integrations/constants.tsx b/x-pack/plugins/fleet/public/applications/integrations/constants.tsx
index 403a47f4b94b2..08197e18fec02 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/constants.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/constants.tsx
@@ -7,7 +7,7 @@
import type { IconType } from '@elastic/eui';
-import type { AssetType, ServiceName } from '../../types';
+import type { ServiceName } from '../../types';
import { ElasticsearchAssetType, KibanaAssetType } from '../../types';
export * from '../../constants';
@@ -20,8 +20,9 @@ export const DisplayedAssets: ServiceNameToAssetTypes = {
kibana: Object.values(KibanaAssetType),
elasticsearch: Object.values(ElasticsearchAssetType),
};
+export type DisplayedAssetType = KibanaAssetType | ElasticsearchAssetType;
-export const AssetTitleMap: Record = {
+export const AssetTitleMap: Record = {
dashboard: 'Dashboard',
ilm_policy: 'ILM Policy',
ingest_pipeline: 'Ingest Pipeline',
@@ -31,7 +32,6 @@ export const AssetTitleMap: Record = {
component_template: 'Component Template',
search: 'Saved Search',
visualization: 'Visualization',
- input: 'Agent input',
map: 'Map',
data_stream_ilm_policy: 'Data Stream ILM Policy',
lens: 'Lens',
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx
index 6ddff968bd3f3..41db09b0538b9 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/constants.tsx
@@ -7,7 +7,7 @@
import type { IconType } from '@elastic/eui';
-import type { AssetType, ServiceName } from '../../types';
+import type { ServiceName } from '../../types';
import { ElasticsearchAssetType, KibanaAssetType } from '../../types';
// only allow Kibana assets for the kibana key, ES asssets for elasticsearch, etc
@@ -19,7 +19,9 @@ export const DisplayedAssets: ServiceNameToAssetTypes = {
elasticsearch: Object.values(ElasticsearchAssetType),
};
-export const AssetTitleMap: Record = {
+export type DisplayedAssetType = ElasticsearchAssetType | KibanaAssetType;
+
+export const AssetTitleMap: Record = {
dashboard: 'Dashboard',
ilm_policy: 'ILM Policy',
ingest_pipeline: 'Ingest Pipeline',
@@ -29,7 +31,6 @@ export const AssetTitleMap: Record = {
component_template: 'Component Template',
search: 'Saved Search',
visualization: 'Visualization',
- input: 'Agent input',
map: 'Map',
data_stream_ilm_policy: 'Data Stream ILM Policy',
lens: 'Lens',
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx
index 487df17980345..0a601d2128bba 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/details.tsx
@@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import React, { memo, useMemo } from 'react';
+import React, { memo, useCallback, useMemo, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiFlexGroup,
@@ -13,6 +13,8 @@ import {
EuiTextColor,
EuiDescriptionList,
EuiNotificationBadge,
+ EuiLink,
+ EuiPortal,
} from '@elastic/eui';
import type { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list';
@@ -26,6 +28,8 @@ import { entries } from '../../../../../types';
import { useGetCategories } from '../../../../../hooks';
import { AssetTitleMap, DisplayedAssets, ServiceTitleMap } from '../../../constants';
+import { NoticeModal } from './notice_modal';
+
interface Props {
packageInfo: PackageInfo;
}
@@ -41,6 +45,11 @@ export const Details: React.FC = memo(({ packageInfo }) => {
return [];
}, [categoriesData, isLoadingCategories, packageInfo.categories]);
+ const [isNoticeModalOpen, setIsNoticeModalOpen] = useState(false);
+ const toggleNoticeModal = useCallback(() => {
+ setIsNoticeModalOpen(!isNoticeModalOpen);
+ }, [isNoticeModalOpen]);
+
const listItems = useMemo(() => {
// Base details: version and categories
const items: EuiDescriptionListProps['listItems'] = [
@@ -123,14 +132,23 @@ export const Details: React.FC = memo(({ packageInfo }) => {
}
// License details
- if (packageInfo.license) {
+ if (packageInfo.license || packageInfo.notice) {
items.push({
title: (
),
- description: packageInfo.license,
+ description: (
+ <>
+ {packageInfo.license}
+ {packageInfo.notice && (
+
+ NOTICE.txt
+
+ )}
+ >
+ ),
});
}
@@ -140,21 +158,30 @@ export const Details: React.FC = memo(({ packageInfo }) => {
packageInfo.assets,
packageInfo.data_streams,
packageInfo.license,
+ packageInfo.notice,
packageInfo.version,
+ toggleNoticeModal,
]);
return (
-
-
-
-
-
-
-
-
-
-
-
-
+ <>
+
+ {isNoticeModalOpen && packageInfo.notice && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
);
});
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx
new file mode 100644
index 0000000000000..239bd133d3c91
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/notice_modal.tsx
@@ -0,0 +1,79 @@
+/*
+ * 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, { useEffect, useState } from 'react';
+import {
+ EuiCodeBlock,
+ EuiLoadingContent,
+ EuiModal,
+ EuiModalBody,
+ EuiModalHeader,
+ EuiModalFooter,
+ EuiModalHeaderTitle,
+ EuiButton,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { sendGetFileByPath, useStartServices } from '../../../../../hooks';
+
+interface Props {
+ noticePath: string;
+ onClose: () => void;
+}
+
+export const NoticeModal: React.FunctionComponent = ({ noticePath, onClose }) => {
+ const { notifications } = useStartServices();
+ const [notice, setNotice] = useState(undefined);
+
+ useEffect(() => {
+ async function fetchData() {
+ try {
+ const { data } = await sendGetFileByPath(noticePath);
+ setNotice(data || '');
+ } catch (err) {
+ notifications.toasts.addError(err, {
+ title: i18n.translate('xpack.fleet.epm.errorLoadingNotice', {
+ defaultMessage: 'Error loading NOTICE.txt',
+ }),
+ });
+ }
+ }
+ fetchData();
+ }, [noticePath, notifications]);
+ return (
+
+
+
+ NOTICE.txt
+
+
+
+
+ {notice ? (
+ notice
+ ) : (
+ // Simulate a long notice while loading
+ <>
+
+
+
+
+
+
+ >
+ )}
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/fleet/server/services/epm/archive/index.ts b/x-pack/plugins/fleet/server/services/epm/archive/index.ts
index 809684df0592c..b08ec815a394d 100644
--- a/x-pack/plugins/fleet/server/services/epm/archive/index.ts
+++ b/x-pack/plugins/fleet/server/services/epm/archive/index.ts
@@ -114,6 +114,13 @@ export function getPathParts(path: string): AssetParts {
[pkgkey, service, type, file] = path.replace(`data_stream/${dataset}/`, '').split('/');
}
+ // To support the NOTICE asset at the root level
+ if (service === 'NOTICE.txt') {
+ file = service;
+ type = 'notice';
+ service = '';
+ }
+
// This is to cover for the fields.yml files inside the "fields" directory
if (file === undefined) {
file = type;
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 e4e4f9c55fd2b..404431816d10c 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts
@@ -126,6 +126,7 @@ export async function getPackageInfo(options: {
title: packageInfo.title || nameAsTitle(packageInfo.name),
assets: Registry.groupPathsByService(paths || []),
removable: !isRequiredPackage(pkgName),
+ notice: Registry.getNoticePath(paths || []),
};
const updated = { ...packageInfo, ...additions };
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 5ee7e198555c5..011a0e74e8c18 100644
--- a/x-pack/plugins/fleet/server/services/epm/registry/index.ts
+++ b/x-pack/plugins/fleet/server/services/epm/registry/index.ts
@@ -255,3 +255,15 @@ export function groupPathsByService(paths: string[]): AssetsGroupedByServiceByTy
elasticsearch: assets.elasticsearch,
};
}
+
+export function getNoticePath(paths: string[]): string | undefined {
+ for (const path of paths) {
+ const parts = getPathParts(path.replace(/^\/package\//, ''));
+ if (parts.type === 'notice') {
+ const { pkgName, pkgVersion } = splitPkgKey(parts.pkgkey);
+ return `/package/${pkgName}/${pkgVersion}/${parts.file}`;
+ }
+ }
+
+ return undefined;
+}
From 571524005ee9913f50eaac73a8689c0e33439e3b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Casper=20H=C3=BCbertz?=
Date: Mon, 14 Jun 2021 18:34:52 +0200
Subject: [PATCH 11/91] [APM] Change impact indicator size (#102060)
---
.../shared/ImpactBar/__snapshots__/ImpactBar.test.js.snap | 2 +-
x-pack/plugins/apm/public/components/shared/ImpactBar/index.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/apm/public/components/shared/ImpactBar/__snapshots__/ImpactBar.test.js.snap b/x-pack/plugins/apm/public/components/shared/ImpactBar/__snapshots__/ImpactBar.test.js.snap
index 8e89939f585aa..87b5b68e26026 100644
--- a/x-pack/plugins/apm/public/components/shared/ImpactBar/__snapshots__/ImpactBar.test.js.snap
+++ b/x-pack/plugins/apm/public/components/shared/ImpactBar/__snapshots__/ImpactBar.test.js.snap
@@ -4,7 +4,7 @@ exports[`ImpactBar component should render with default values 1`] = `
`;
diff --git a/x-pack/plugins/apm/public/components/shared/ImpactBar/index.tsx b/x-pack/plugins/apm/public/components/shared/ImpactBar/index.tsx
index 92f488b8ba0ee..87b3c669e993c 100644
--- a/x-pack/plugins/apm/public/components/shared/ImpactBar/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/ImpactBar/index.tsx
@@ -18,7 +18,7 @@ export interface ImpactBarProps extends Record {
export function ImpactBar({
value,
- size = 'l',
+ size = 'm',
max = 100,
color = 'primary',
...rest
From 666bce392383a57f983617bc25b901a556ec20b0 Mon Sep 17 00:00:00 2001
From: Dario Gieselaar
Date: Mon, 14 Jun 2021 18:44:29 +0200
Subject: [PATCH 12/91] [APM] Enforce span creation/naming for ES searches
(#101856)
---
packages/kbn-apm-utils/src/index.ts | 51 ++-
.../chart_preview/get_transaction_duration.ts | 120 +++---
.../get_transaction_error_count.ts | 72 ++--
.../get_transaction_error_rate.ts | 5 +-
...et_correlations_for_failed_transactions.ts | 207 +++++-----
.../errors/get_overall_error_timeseries.ts | 58 +--
.../get_correlations_for_slow_transactions.ts | 89 ++---
.../latency/get_duration_for_percentile.ts | 41 +-
.../latency/get_latency_distribution.ts | 116 +++---
.../correlations/latency/get_max_latency.ts | 61 ++-
.../get_overall_latency_distribution.ts | 5 +-
.../lib/environments/get_all_environments.ts | 88 +++--
.../lib/environments/get_environments.ts | 83 ++--
.../__snapshots__/get_buckets.test.ts.snap | 1 +
.../errors/distribution/get_buckets.test.ts | 2 +-
.../lib/errors/distribution/get_buckets.ts | 90 ++---
.../lib/errors/get_error_group_sample.ts | 77 ++--
.../apm/server/lib/errors/get_error_groups.ts | 139 ++++---
.../helpers/aggregated_transactions/index.ts | 18 +-
.../create_es_client/call_async_with_debug.ts | 21 +-
.../create_apm_event_client/index.test.ts | 2 +-
.../create_apm_event_client/index.ts | 19 +-
.../create_internal_es_client/index.ts | 44 ++-
.../server/lib/helpers/setup_request.test.ts | 32 +-
.../java/gc/fetch_and_transform_gc_metrics.ts | 4 +-
.../by_agent/java/gc/get_gc_rate_chart.ts | 22 +-
.../by_agent/java/gc/get_gc_time_chart.ts | 22 +-
.../by_agent/java/heap_memory/index.ts | 34 +-
.../by_agent/java/non_heap_memory/index.ts | 38 +-
.../by_agent/java/thread_count/index.ts | 30 +-
.../lib/metrics/by_agent/shared/cpu/index.ts | 32 +-
.../metrics/by_agent/shared/memory/index.ts | 70 ++--
.../metrics/fetch_and_transform_metrics.ts | 4 +-
.../get_service_count.ts | 50 +--
.../get_transactions_per_minute.ts | 113 +++---
.../lib/observability_overview/has_data.ts | 48 +--
.../lib/rum_client/get_client_metrics.ts | 2 +-
.../server/lib/rum_client/get_js_errors.ts | 2 +-
.../lib/rum_client/get_long_task_metrics.ts | 2 +-
.../rum_client/get_page_load_distribution.ts | 7 +-
.../lib/rum_client/get_page_view_trends.ts | 2 +-
.../lib/rum_client/get_pl_dist_breakdown.ts | 5 +-
.../server/lib/rum_client/get_rum_services.ts | 2 +-
.../server/lib/rum_client/get_url_search.ts | 2 +-
.../lib/rum_client/get_visitor_breakdown.ts | 2 +-
.../lib/rum_client/get_web_core_vitals.ts | 2 +-
.../apm/server/lib/rum_client/has_rum_data.ts | 2 +-
.../ui_filters/local_ui_filters/index.ts | 55 +--
.../fetch_service_paths_from_trace_ids.ts | 120 +++---
.../server/lib/service_map/get_service_map.ts | 101 ++---
.../get_service_map_service_node_info.ts | 119 +++---
.../lib/service_map/get_trace_sample_ids.ts | 195 +++++-----
.../apm/server/lib/service_nodes/index.ts | 101 +++--
.../__snapshots__/queries.test.ts.snap | 1 +
.../get_derived_service_annotations.ts | 142 ++++---
.../lib/services/get_service_agent_name.ts | 68 ++--
.../get_destination_map.ts | 137 ++++---
.../get_service_dependencies/get_metrics.ts | 94 ++---
...service_error_group_detailed_statistics.ts | 111 +++---
...get_service_error_group_main_statistics.ts | 45 ++-
.../get_service_error_groups/index.ts | 76 ++--
.../get_service_instance_metadata_details.ts | 56 +--
...vice_instances_system_metric_statistics.ts | 259 +++++++------
...ervice_instances_transaction_statistics.ts | 221 ++++++-----
.../services/get_service_metadata_details.ts | 172 ++++-----
.../services/get_service_metadata_icons.ts | 92 ++---
.../lib/services/get_service_node_metadata.ts | 69 ++--
...e_transaction_group_detailed_statistics.ts | 191 +++++-----
.../get_service_transaction_groups.ts | 86 ++---
.../services/get_service_transaction_types.ts | 68 ++--
.../get_services/get_legacy_data_status.ts | 44 +--
.../get_service_transaction_stats.ts | 142 +++----
.../get_services_from_metric_documents.ts | 36 +-
.../get_services/has_historical_agent_data.ts | 35 +-
.../apm/server/lib/services/get_throughput.ts | 25 +-
.../get_service_profiling_statistics.ts | 125 +++----
.../get_service_profiling_timeline.ts | 50 +--
.../apm/server/lib/services/queries.test.ts | 2 +-
.../create_or_update_configuration.ts | 43 +--
.../delete_configuration.ts | 17 +-
.../find_exact_configuration.ts | 56 ++-
.../get_agent_name_by_service.ts | 54 +--
.../get_existing_environments_for_service.ts | 54 +--
.../agent_configuration/get_service_names.ts | 66 ++--
.../list_configurations.ts | 6 +-
.../mark_applied_by_agent.ts | 5 +-
.../search_configurations.ts | 98 +++--
.../create_or_update_custom_link.test.ts | 52 +--
.../create_or_update_custom_link.ts | 31 +-
.../custom_link/delete_custom_link.ts | 17 +-
.../settings/custom_link/get_transaction.ts | 56 +--
.../settings/custom_link/list_custom_links.ts | 78 ++--
.../apm/server/lib/traces/get_trace_items.ts | 166 ++++----
.../lib/transaction_groups/get_error_rate.ts | 132 +++----
.../get_transaction_group_stats.ts | 187 +++++-----
.../lib/transaction_groups/queries.test.ts | 4 +-
.../lib/transactions/breakdown/index.ts | 353 +++++++++---------
.../distribution/get_buckets/index.ts | 128 ++++---
.../distribution/get_distribution_max.ts | 70 ++--
.../transactions/get_latency_charts/index.ts | 69 ++--
.../get_throughput_charts/index.ts | 41 +-
.../lib/transactions/get_transaction/index.ts | 39 +-
.../get_transaction_by_trace/index.ts | 59 +--
.../plugins/apm/server/utils/test_helpers.tsx | 2 +-
.../plugins/apm/server/utils/with_apm_span.ts | 2 +-
105 files changed, 3410 insertions(+), 3451 deletions(-)
diff --git a/packages/kbn-apm-utils/src/index.ts b/packages/kbn-apm-utils/src/index.ts
index 384b6683199e5..09a6989091f60 100644
--- a/packages/kbn-apm-utils/src/index.ts
+++ b/packages/kbn-apm-utils/src/index.ts
@@ -14,6 +14,7 @@ export interface SpanOptions {
type?: string;
subtype?: string;
labels?: Record;
+ intercept?: boolean;
}
type Span = Exclude;
@@ -36,23 +37,27 @@ export async function withSpan(
): Promise {
const options = parseSpanOptions(optionsOrName);
- const { name, type, subtype, labels } = options;
+ const { name, type, subtype, labels, intercept } = options;
if (!agent.isStarted()) {
return cb();
}
+ let createdSpan: Span | undefined;
+
// When a span starts, it's marked as the active span in its context.
// When it ends, it's not untracked, which means that if a span
// starts directly after this one ends, the newly started span is a
// child of this span, even though it should be a sibling.
// To mitigate this, we queue a microtask by awaiting a promise.
- await Promise.resolve();
+ if (!intercept) {
+ await Promise.resolve();
- const span = agent.startSpan(name);
+ createdSpan = agent.startSpan(name) ?? undefined;
- if (!span) {
- return cb();
+ if (!createdSpan) {
+ return cb();
+ }
}
// If a span is created in the same context as the span that we just
@@ -61,33 +66,51 @@ export async function withSpan(
// mitigate this we create a new context.
return runInNewContext(() => {
+ const promise = cb(createdSpan);
+
+ let span: Span | undefined = createdSpan;
+
+ if (intercept) {
+ span = agent.currentSpan ?? undefined;
+ }
+
+ if (!span) {
+ return promise;
+ }
+
+ const targetedSpan = span;
+
+ if (name) {
+ targetedSpan.name = name;
+ }
+
// @ts-ignore
if (type) {
- span.type = type;
+ targetedSpan.type = type;
}
if (subtype) {
- span.subtype = subtype;
+ targetedSpan.subtype = subtype;
}
if (labels) {
- span.addLabels(labels);
+ targetedSpan.addLabels(labels);
}
- return cb(span)
+ return promise
.then((res) => {
- if (!span.outcome || span.outcome === 'unknown') {
- span.outcome = 'success';
+ if (!targetedSpan.outcome || targetedSpan.outcome === 'unknown') {
+ targetedSpan.outcome = 'success';
}
return res;
})
.catch((err) => {
- if (!span.outcome || span.outcome === 'unknown') {
- span.outcome = 'failure';
+ if (!targetedSpan.outcome || targetedSpan.outcome === 'unknown') {
+ targetedSpan.outcome = 'failure';
}
throw err;
})
.finally(() => {
- span.end();
+ targetedSpan.end();
});
});
}
diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts
index 091982598d6a3..6ce175fcb8362 100644
--- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts
+++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts
@@ -15,82 +15,82 @@ import {
import { ProcessorEvent } from '../../../../common/processor_event';
import { environmentQuery, rangeQuery } from '../../../../server/utils/queries';
import { AlertParams } from '../../../routes/alerts/chart_preview';
-import { withApmSpan } from '../../../utils/with_apm_span';
import { getBucketSize } from '../../helpers/get_bucket_size';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
-export function getTransactionDurationChartPreview({
+export async function getTransactionDurationChartPreview({
alertParams,
setup,
}: {
alertParams: AlertParams;
setup: Setup & SetupTimeRange;
}) {
- return withApmSpan('get_transaction_duration_chart_preview', async () => {
- const { apmEventClient, start, end } = setup;
- const {
- aggregationType,
- environment,
- serviceName,
- transactionType,
- } = alertParams;
+ const { apmEventClient, start, end } = setup;
+ const {
+ aggregationType,
+ environment,
+ serviceName,
+ transactionType,
+ } = alertParams;
- const query = {
- bool: {
- filter: [
- { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } },
- ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []),
- ...(transactionType
- ? [{ term: { [TRANSACTION_TYPE]: transactionType } }]
- : []),
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ] as QueryDslQueryContainer[],
- },
- };
+ const query = {
+ bool: {
+ filter: [
+ { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } },
+ ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []),
+ ...(transactionType
+ ? [{ term: { [TRANSACTION_TYPE]: transactionType } }]
+ : []),
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ] as QueryDslQueryContainer[],
+ },
+ };
- const { intervalString } = getBucketSize({ start, end, numBuckets: 20 });
+ const { intervalString } = getBucketSize({ start, end, numBuckets: 20 });
- const aggs = {
- timeseries: {
- date_histogram: {
- field: '@timestamp',
- fixed_interval: intervalString,
- },
- aggs: {
- agg:
- aggregationType === 'avg'
- ? { avg: { field: TRANSACTION_DURATION } }
- : {
- percentiles: {
- field: TRANSACTION_DURATION,
- percents: [aggregationType === '95th' ? 95 : 99],
- },
+ const aggs = {
+ timeseries: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: intervalString,
+ },
+ aggs: {
+ agg:
+ aggregationType === 'avg'
+ ? { avg: { field: TRANSACTION_DURATION } }
+ : {
+ percentiles: {
+ field: TRANSACTION_DURATION,
+ percents: [aggregationType === '95th' ? 95 : 99],
},
- },
+ },
},
- };
- const params = {
- apm: { events: [ProcessorEvent.transaction] },
- body: { size: 0, query, aggs },
- };
- const resp = await apmEventClient.search(params);
+ },
+ };
+ const params = {
+ apm: { events: [ProcessorEvent.transaction] },
+ body: { size: 0, query, aggs },
+ };
+ const resp = await apmEventClient.search(
+ 'get_transaction_duration_chart_preview',
+ params
+ );
- if (!resp.aggregations) {
- return [];
- }
+ if (!resp.aggregations) {
+ return [];
+ }
- return resp.aggregations.timeseries.buckets.map((bucket) => {
- const percentilesKey = aggregationType === '95th' ? '95.0' : '99.0';
- const x = bucket.key;
- const y =
- aggregationType === 'avg'
- ? (bucket.agg as { value: number | null }).value
- : (bucket.agg as { values: Record }).values[
- percentilesKey
- ];
+ return resp.aggregations.timeseries.buckets.map((bucket) => {
+ const percentilesKey = aggregationType === '95th' ? '95.0' : '99.0';
+ const x = bucket.key;
+ const y =
+ aggregationType === 'avg'
+ ? (bucket.agg as { value: number | null }).value
+ : (bucket.agg as { values: Record }).values[
+ percentilesKey
+ ];
- return { x, y };
- });
+ return { x, y };
});
}
diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts
index 2cf1317dc44b0..3d64c63cb2041 100644
--- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts
+++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts
@@ -9,58 +9,58 @@ import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames';
import { ProcessorEvent } from '../../../../common/processor_event';
import { AlertParams } from '../../../routes/alerts/chart_preview';
import { environmentQuery, rangeQuery } from '../../../../server/utils/queries';
-import { withApmSpan } from '../../../utils/with_apm_span';
import { getBucketSize } from '../../helpers/get_bucket_size';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
-export function getTransactionErrorCountChartPreview({
+export async function getTransactionErrorCountChartPreview({
setup,
alertParams,
}: {
setup: Setup & SetupTimeRange;
alertParams: AlertParams;
}) {
- return withApmSpan('get_transaction_error_count_chart_preview', async () => {
- const { apmEventClient, start, end } = setup;
- const { serviceName, environment } = alertParams;
+ const { apmEventClient, start, end } = setup;
+ const { serviceName, environment } = alertParams;
- const query = {
- bool: {
- filter: [
- ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []),
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ],
- },
- };
+ const query = {
+ bool: {
+ filter: [
+ ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []),
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ],
+ },
+ };
- const { intervalString } = getBucketSize({ start, end, numBuckets: 20 });
+ const { intervalString } = getBucketSize({ start, end, numBuckets: 20 });
- const aggs = {
- timeseries: {
- date_histogram: {
- field: '@timestamp',
- fixed_interval: intervalString,
- },
+ const aggs = {
+ timeseries: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: intervalString,
},
- };
+ },
+ };
- const params = {
- apm: { events: [ProcessorEvent.error] },
- body: { size: 0, query, aggs },
- };
+ const params = {
+ apm: { events: [ProcessorEvent.error] },
+ body: { size: 0, query, aggs },
+ };
- const resp = await apmEventClient.search(params);
+ const resp = await apmEventClient.search(
+ 'get_transaction_error_count_chart_preview',
+ params
+ );
- if (!resp.aggregations) {
- return [];
- }
+ if (!resp.aggregations) {
+ return [];
+ }
- return resp.aggregations.timeseries.buckets.map((bucket) => {
- return {
- x: bucket.key,
- y: bucket.doc_count,
- };
- });
+ return resp.aggregations.timeseries.buckets.map((bucket) => {
+ return {
+ x: bucket.key,
+ y: bucket.doc_count,
+ };
});
}
diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts
index f0c8d23e0e8fa..0a6a25ad9c533 100644
--- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts
+++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts
@@ -64,7 +64,10 @@ export async function getTransactionErrorRateChartPreview({
body: { size: 0, query, aggs },
};
- const resp = await apmEventClient.search(params);
+ const resp = await apmEventClient.search(
+ 'get_transaction_error_rate_chart_preview',
+ params
+ );
if (!resp.aggregations) {
return [];
diff --git a/x-pack/plugins/apm/server/lib/correlations/errors/get_correlations_for_failed_transactions.ts b/x-pack/plugins/apm/server/lib/correlations/errors/get_correlations_for_failed_transactions.ts
index 8ee469c9a93c7..11e9f99ddb356 100644
--- a/x-pack/plugins/apm/server/lib/correlations/errors/get_correlations_for_failed_transactions.ts
+++ b/x-pack/plugins/apm/server/lib/correlations/errors/get_correlations_for_failed_transactions.ts
@@ -21,68 +21,68 @@ import {
getTimeseriesAggregation,
getTransactionErrorRateTimeSeries,
} from '../../helpers/transaction_error_rate';
-import { withApmSpan } from '../../../utils/with_apm_span';
import { CorrelationsOptions, getCorrelationsFilters } from '../get_filters';
interface Options extends CorrelationsOptions {
fieldNames: string[];
}
export async function getCorrelationsForFailedTransactions(options: Options) {
- return withApmSpan('get_correlations_for_failed_transactions', async () => {
- const { fieldNames, setup } = options;
- const { apmEventClient } = setup;
- const filters = getCorrelationsFilters(options);
-
- const params = {
- apm: { events: [ProcessorEvent.transaction] },
- track_total_hits: true,
- body: {
- size: 0,
- query: {
- bool: { filter: filters },
- },
- aggs: {
- failed_transactions: {
- filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } },
-
- // significant term aggs
- aggs: fieldNames.reduce((acc, fieldName) => {
- return {
- ...acc,
- [fieldName]: {
- significant_terms: {
- size: 10,
- field: fieldName,
- background_filter: {
- bool: {
- filter: filters,
- must_not: {
- term: { [EVENT_OUTCOME]: EventOutcome.failure },
- },
+ const { fieldNames, setup } = options;
+ const { apmEventClient } = setup;
+ const filters = getCorrelationsFilters(options);
+
+ const params = {
+ apm: { events: [ProcessorEvent.transaction] },
+ track_total_hits: true,
+ body: {
+ size: 0,
+ query: {
+ bool: { filter: filters },
+ },
+ aggs: {
+ failed_transactions: {
+ filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } },
+
+ // significant term aggs
+ aggs: fieldNames.reduce((acc, fieldName) => {
+ return {
+ ...acc,
+ [fieldName]: {
+ significant_terms: {
+ size: 10,
+ field: fieldName,
+ background_filter: {
+ bool: {
+ filter: filters,
+ must_not: {
+ term: { [EVENT_OUTCOME]: EventOutcome.failure },
},
},
},
},
- };
- }, {} as Record),
- },
+ },
+ };
+ }, {} as Record),
},
},
- };
-
- const response = await apmEventClient.search(params);
- if (!response.aggregations) {
- return { significantTerms: [] };
- }
-
- const sigTermAggs = omit(
- response.aggregations?.failed_transactions,
- 'doc_count'
- );
-
- const topSigTerms = processSignificantTermAggs({ sigTermAggs });
- return getErrorRateTimeSeries({ setup, filters, topSigTerms });
- });
+ },
+ };
+
+ const response = await apmEventClient.search(
+ 'get_correlations_for_failed_transactions',
+ params
+ );
+ if (!response.aggregations) {
+ return { significantTerms: [] };
+ }
+
+ const sigTermAggs = omit(
+ response.aggregations?.failed_transactions,
+ 'doc_count'
+ );
+
+ const topSigTerms = processSignificantTermAggs({ sigTermAggs });
+ return getErrorRateTimeSeries({ setup, filters, topSigTerms });
}
export async function getErrorRateTimeSeries({
@@ -94,58 +94,59 @@ export async function getErrorRateTimeSeries({
filters: ESFilter[];
topSigTerms: TopSigTerm[];
}) {
- return withApmSpan('get_error_rate_timeseries', async () => {
- const { start, end, apmEventClient } = setup;
- const { intervalString } = getBucketSize({ start, end, numBuckets: 15 });
-
- if (isEmpty(topSigTerms)) {
- return { significantTerms: [] };
+ const { start, end, apmEventClient } = setup;
+ const { intervalString } = getBucketSize({ start, end, numBuckets: 15 });
+
+ if (isEmpty(topSigTerms)) {
+ return { significantTerms: [] };
+ }
+
+ const timeseriesAgg = getTimeseriesAggregation(start, end, intervalString);
+
+ const perTermAggs = topSigTerms.reduce(
+ (acc, term, index) => {
+ acc[`term_${index}`] = {
+ filter: { term: { [term.fieldName]: term.fieldValue } },
+ aggs: { timeseries: timeseriesAgg },
+ };
+ return acc;
+ },
+ {} as {
+ [key: string]: {
+ filter: AggregationOptionsByType['filter'];
+ aggs: { timeseries: typeof timeseriesAgg };
+ };
}
-
- const timeseriesAgg = getTimeseriesAggregation(start, end, intervalString);
-
- const perTermAggs = topSigTerms.reduce(
- (acc, term, index) => {
- acc[`term_${index}`] = {
- filter: { term: { [term.fieldName]: term.fieldValue } },
- aggs: { timeseries: timeseriesAgg },
- };
- return acc;
- },
- {} as {
- [key: string]: {
- filter: AggregationOptionsByType['filter'];
- aggs: { timeseries: typeof timeseriesAgg };
- };
- }
- );
-
- const params = {
- // TODO: add support for metrics
- apm: { events: [ProcessorEvent.transaction] },
- body: {
- size: 0,
- query: { bool: { filter: filters } },
- aggs: perTermAggs,
- },
- };
-
- const response = await apmEventClient.search(params);
- const { aggregations } = response;
-
- if (!aggregations) {
- return { significantTerms: [] };
- }
-
- return {
- significantTerms: topSigTerms.map((topSig, index) => {
- const agg = aggregations[`term_${index}`]!;
-
- return {
- ...topSig,
- timeseries: getTransactionErrorRateTimeSeries(agg.timeseries.buckets),
- };
- }),
- };
- });
+ );
+
+ const params = {
+ // TODO: add support for metrics
+ apm: { events: [ProcessorEvent.transaction] },
+ body: {
+ size: 0,
+ query: { bool: { filter: filters } },
+ aggs: perTermAggs,
+ },
+ };
+
+ const response = await apmEventClient.search(
+ 'get_error_rate_timeseries',
+ params
+ );
+ const { aggregations } = response;
+
+ if (!aggregations) {
+ return { significantTerms: [] };
+ }
+
+ return {
+ significantTerms: topSigTerms.map((topSig, index) => {
+ const agg = aggregations[`term_${index}`]!;
+
+ return {
+ ...topSig,
+ timeseries: getTransactionErrorRateTimeSeries(agg.timeseries.buckets),
+ };
+ }),
+ };
}
diff --git a/x-pack/plugins/apm/server/lib/correlations/errors/get_overall_error_timeseries.ts b/x-pack/plugins/apm/server/lib/correlations/errors/get_overall_error_timeseries.ts
index 9387e64a51e01..f3477273806b6 100644
--- a/x-pack/plugins/apm/server/lib/correlations/errors/get_overall_error_timeseries.ts
+++ b/x-pack/plugins/apm/server/lib/correlations/errors/get_overall_error_timeseries.ts
@@ -11,41 +11,41 @@ import {
getTimeseriesAggregation,
getTransactionErrorRateTimeSeries,
} from '../../helpers/transaction_error_rate';
-import { withApmSpan } from '../../../utils/with_apm_span';
import { CorrelationsOptions, getCorrelationsFilters } from '../get_filters';
export async function getOverallErrorTimeseries(options: CorrelationsOptions) {
- return withApmSpan('get_error_rate_timeseries', async () => {
- const { setup } = options;
- const filters = getCorrelationsFilters(options);
- const { start, end, apmEventClient } = setup;
- const { intervalString } = getBucketSize({ start, end, numBuckets: 15 });
+ const { setup } = options;
+ const filters = getCorrelationsFilters(options);
+ const { start, end, apmEventClient } = setup;
+ const { intervalString } = getBucketSize({ start, end, numBuckets: 15 });
- const params = {
- // TODO: add support for metrics
- apm: { events: [ProcessorEvent.transaction] },
- body: {
- size: 0,
- query: { bool: { filter: filters } },
- aggs: {
- timeseries: getTimeseriesAggregation(start, end, intervalString),
- },
+ const params = {
+ // TODO: add support for metrics
+ apm: { events: [ProcessorEvent.transaction] },
+ body: {
+ size: 0,
+ query: { bool: { filter: filters } },
+ aggs: {
+ timeseries: getTimeseriesAggregation(start, end, intervalString),
},
- };
+ },
+ };
- const response = await apmEventClient.search(params);
- const { aggregations } = response;
+ const response = await apmEventClient.search(
+ 'get_error_rate_timeseries',
+ params
+ );
+ const { aggregations } = response;
- if (!aggregations) {
- return { overall: null };
- }
+ if (!aggregations) {
+ return { overall: null };
+ }
- return {
- overall: {
- timeseries: getTransactionErrorRateTimeSeries(
- aggregations.timeseries.buckets
- ),
- },
- };
- });
+ return {
+ overall: {
+ timeseries: getTransactionErrorRateTimeSeries(
+ aggregations.timeseries.buckets
+ ),
+ },
+ };
}
diff --git a/x-pack/plugins/apm/server/lib/correlations/latency/get_correlations_for_slow_transactions.ts b/x-pack/plugins/apm/server/lib/correlations/latency/get_correlations_for_slow_transactions.ts
index 0f93d1411a001..c37b3e3ab8242 100644
--- a/x-pack/plugins/apm/server/lib/correlations/latency/get_correlations_for_slow_transactions.ts
+++ b/x-pack/plugins/apm/server/lib/correlations/latency/get_correlations_for_slow_transactions.ts
@@ -41,60 +41,63 @@ export async function getCorrelationsForSlowTransactions(options: Options) {
return { significantTerms: [] };
}
- const response = await withApmSpan('get_significant_terms', () => {
- const params = {
- apm: { events: [ProcessorEvent.transaction] },
- body: {
- size: 0,
- query: {
- bool: {
- // foreground filters
- filter: filters,
- must: {
- function_score: {
- query: {
- range: {
- [TRANSACTION_DURATION]: { gte: durationForPercentile },
- },
+ const params = {
+ apm: { events: [ProcessorEvent.transaction] },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ // foreground filters
+ filter: filters,
+ must: {
+ function_score: {
+ query: {
+ range: {
+ [TRANSACTION_DURATION]: { gte: durationForPercentile },
},
- script_score: {
- script: {
- source: `Math.log(2 + doc['${TRANSACTION_DURATION}'].value)`,
- },
+ },
+ script_score: {
+ script: {
+ source: `Math.log(2 + doc['${TRANSACTION_DURATION}'].value)`,
},
},
},
},
},
- aggs: fieldNames.reduce((acc, fieldName) => {
- return {
- ...acc,
- [fieldName]: {
- significant_terms: {
- size: 10,
- field: fieldName,
- background_filter: {
- bool: {
- filter: [
- ...filters,
- {
- range: {
- [TRANSACTION_DURATION]: {
- lt: durationForPercentile,
- },
+ },
+ aggs: fieldNames.reduce((acc, fieldName) => {
+ return {
+ ...acc,
+ [fieldName]: {
+ significant_terms: {
+ size: 10,
+ field: fieldName,
+ background_filter: {
+ bool: {
+ filter: [
+ ...filters,
+ {
+ range: {
+ [TRANSACTION_DURATION]: {
+ lt: durationForPercentile,
},
},
- ],
- },
+ },
+ ],
},
},
},
- };
- }, {} as Record),
- },
- };
- return apmEventClient.search(params);
- });
+ },
+ };
+ }, {} as Record),
+ },
+ };
+
+ const response = await apmEventClient.search(
+ 'get_significant_terms',
+ params
+ );
+
if (!response.aggregations) {
return { significantTerms: [] };
}
diff --git a/x-pack/plugins/apm/server/lib/correlations/latency/get_duration_for_percentile.ts b/x-pack/plugins/apm/server/lib/correlations/latency/get_duration_for_percentile.ts
index 43c261743861d..a686980700d83 100644
--- a/x-pack/plugins/apm/server/lib/correlations/latency/get_duration_for_percentile.ts
+++ b/x-pack/plugins/apm/server/lib/correlations/latency/get_duration_for_percentile.ts
@@ -8,7 +8,6 @@
import { ESFilter } from '../../../../../../../typings/elasticsearch';
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
import { ProcessorEvent } from '../../../../common/processor_event';
-import { withApmSpan } from '../../../utils/with_apm_span';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
export async function getDurationForPercentile({
@@ -20,31 +19,27 @@ export async function getDurationForPercentile({
filters: ESFilter[];
setup: Setup & SetupTimeRange;
}) {
- return withApmSpan('get_duration_for_percentiles', async () => {
- const { apmEventClient } = setup;
- const res = await apmEventClient.search({
- apm: {
- events: [ProcessorEvent.transaction],
+ const { apmEventClient } = setup;
+ const res = await apmEventClient.search('get_duration_for_percentiles', {
+ apm: {
+ events: [ProcessorEvent.transaction],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: { filter: filters },
},
- body: {
- size: 0,
- query: {
- bool: { filter: filters },
- },
- aggs: {
- percentile: {
- percentiles: {
- field: TRANSACTION_DURATION,
- percents: [durationPercentile],
- },
+ aggs: {
+ percentile: {
+ percentiles: {
+ field: TRANSACTION_DURATION,
+ percents: [durationPercentile],
},
},
},
- });
-
- const duration = Object.values(
- res.aggregations?.percentile.values || {}
- )[0];
- return duration || 0;
+ },
});
+
+ const duration = Object.values(res.aggregations?.percentile.values || {})[0];
+ return duration || 0;
}
diff --git a/x-pack/plugins/apm/server/lib/correlations/latency/get_latency_distribution.ts b/x-pack/plugins/apm/server/lib/correlations/latency/get_latency_distribution.ts
index 6d42b26b22e42..be1bb631378cf 100644
--- a/x-pack/plugins/apm/server/lib/correlations/latency/get_latency_distribution.ts
+++ b/x-pack/plugins/apm/server/lib/correlations/latency/get_latency_distribution.ts
@@ -10,7 +10,7 @@ import { ESFilter } from '../../../../../../../typings/elasticsearch';
import { ProcessorEvent } from '../../../../common/processor_event';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
import { TopSigTerm } from '../process_significant_term_aggs';
-import { withApmSpan } from '../../../utils/with_apm_span';
+
import {
getDistributionAggregation,
trimBuckets,
@@ -29,70 +29,70 @@ export async function getLatencyDistribution({
maxLatency: number;
distributionInterval: number;
}) {
- return withApmSpan('get_latency_distribution', async () => {
- const { apmEventClient } = setup;
+ const { apmEventClient } = setup;
- const distributionAgg = getDistributionAggregation(
- maxLatency,
- distributionInterval
- );
+ const distributionAgg = getDistributionAggregation(
+ maxLatency,
+ distributionInterval
+ );
- const perTermAggs = topSigTerms.reduce(
- (acc, term, index) => {
- acc[`term_${index}`] = {
- filter: { term: { [term.fieldName]: term.fieldValue } },
- aggs: {
- distribution: distributionAgg,
- },
+ const perTermAggs = topSigTerms.reduce(
+ (acc, term, index) => {
+ acc[`term_${index}`] = {
+ filter: { term: { [term.fieldName]: term.fieldValue } },
+ aggs: {
+ distribution: distributionAgg,
+ },
+ };
+ return acc;
+ },
+ {} as Record<
+ string,
+ {
+ filter: AggregationOptionsByType['filter'];
+ aggs: {
+ distribution: typeof distributionAgg;
};
- return acc;
- },
- {} as Record<
- string,
- {
- filter: AggregationOptionsByType['filter'];
- aggs: {
- distribution: typeof distributionAgg;
- };
- }
- >
- );
+ }
+ >
+ );
- const params = {
- // TODO: add support for metrics
- apm: { events: [ProcessorEvent.transaction] },
- body: {
- size: 0,
- query: { bool: { filter: filters } },
- aggs: perTermAggs,
- },
- };
+ const params = {
+ // TODO: add support for metrics
+ apm: { events: [ProcessorEvent.transaction] },
+ body: {
+ size: 0,
+ query: { bool: { filter: filters } },
+ aggs: perTermAggs,
+ },
+ };
- const response = await withApmSpan('get_terms_distribution', () =>
- apmEventClient.search(params)
- );
- type Agg = NonNullable;
+ const response = await apmEventClient.search(
+ 'get_latency_distribution',
+ params
+ );
- if (!response.aggregations) {
- return [];
- }
+ type Agg = NonNullable;
- return topSigTerms.map((topSig, index) => {
- // ignore the typescript error since existence of response.aggregations is already checked:
- // @ts-expect-error
- const agg = response.aggregations[`term_${index}`] as Agg[string];
- const total = agg.distribution.doc_count;
- const buckets = trimBuckets(
- agg.distribution.dist_filtered_by_latency.buckets
- );
+ if (!response.aggregations) {
+ return [];
+ }
- return {
- ...topSig,
- distribution: buckets.map((bucket) => ({
- x: bucket.key,
- y: (bucket.doc_count / total) * 100,
- })),
- };
- });
+ return topSigTerms.map((topSig, index) => {
+ // ignore the typescript error since existence of response.aggregations is already checked:
+ // @ts-expect-error
+ const agg = response.aggregations[`term_${index}`] as Agg[string];
+ const total = agg.distribution.doc_count;
+ const buckets = trimBuckets(
+ agg.distribution.dist_filtered_by_latency.buckets
+ );
+
+ return {
+ ...topSig,
+ distribution: buckets.map((bucket) => ({
+ x: bucket.key,
+ y: (bucket.doc_count / total) * 100,
+ })),
+ };
});
}
diff --git a/x-pack/plugins/apm/server/lib/correlations/latency/get_max_latency.ts b/x-pack/plugins/apm/server/lib/correlations/latency/get_max_latency.ts
index 8b415bf0d80a7..f2762086614b4 100644
--- a/x-pack/plugins/apm/server/lib/correlations/latency/get_max_latency.ts
+++ b/x-pack/plugins/apm/server/lib/correlations/latency/get_max_latency.ts
@@ -8,7 +8,6 @@
import { ESFilter } from '../../../../../../../typings/elasticsearch';
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
import { ProcessorEvent } from '../../../../common/processor_event';
-import { withApmSpan } from '../../../utils/with_apm_span';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
import { TopSigTerm } from '../process_significant_term_aggs';
@@ -21,41 +20,39 @@ export async function getMaxLatency({
filters: ESFilter[];
topSigTerms?: TopSigTerm[];
}) {
- return withApmSpan('get_max_latency', async () => {
- const { apmEventClient } = setup;
+ const { apmEventClient } = setup;
- const params = {
- // TODO: add support for metrics
- apm: { events: [ProcessorEvent.transaction] },
- body: {
- size: 0,
- query: {
- bool: {
- filter: filters,
+ const params = {
+ // TODO: add support for metrics
+ apm: { events: [ProcessorEvent.transaction] },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: filters,
- ...(topSigTerms.length
- ? {
- // only include docs containing the significant terms
- should: topSigTerms.map((term) => ({
- term: { [term.fieldName]: term.fieldValue },
- })),
- minimum_should_match: 1,
- }
- : null),
- },
+ ...(topSigTerms.length
+ ? {
+ // only include docs containing the significant terms
+ should: topSigTerms.map((term) => ({
+ term: { [term.fieldName]: term.fieldValue },
+ })),
+ minimum_should_match: 1,
+ }
+ : null),
},
- aggs: {
- // TODO: add support for metrics
- // max_latency: { max: { field: TRANSACTION_DURATION } },
- max_latency: {
- percentiles: { field: TRANSACTION_DURATION, percents: [99] },
- },
+ },
+ aggs: {
+ // TODO: add support for metrics
+ // max_latency: { max: { field: TRANSACTION_DURATION } },
+ max_latency: {
+ percentiles: { field: TRANSACTION_DURATION, percents: [99] },
},
},
- };
+ },
+ };
- const response = await apmEventClient.search(params);
- // return response.aggregations?.max_latency.value;
- return Object.values(response.aggregations?.max_latency.values ?? {})[0];
- });
+ const response = await apmEventClient.search('get_max_latency', params);
+ // return response.aggregations?.max_latency.value;
+ return Object.values(response.aggregations?.max_latency.values ?? {})[0];
}
diff --git a/x-pack/plugins/apm/server/lib/correlations/latency/get_overall_latency_distribution.ts b/x-pack/plugins/apm/server/lib/correlations/latency/get_overall_latency_distribution.ts
index c5d4def51ea54..b0e0f22c70366 100644
--- a/x-pack/plugins/apm/server/lib/correlations/latency/get_overall_latency_distribution.ts
+++ b/x-pack/plugins/apm/server/lib/correlations/latency/get_overall_latency_distribution.ts
@@ -71,8 +71,9 @@ export async function getOverallLatencyDistribution(
},
};
- const response = await withApmSpan('get_terms_distribution', () =>
- apmEventClient.search(params)
+ const response = await apmEventClient.search(
+ 'get_terms_distribution',
+ params
);
if (!response.aggregations) {
diff --git a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts
index 1bf01c24776fb..f6a1987974853 100644
--- a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts
+++ b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts
@@ -13,7 +13,6 @@ import {
} from '../../../common/elasticsearch_fieldnames';
import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values';
import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions';
-import { withApmSpan } from '../../utils/with_apm_span';
/**
* This is used for getting *all* environments, and does not filter by range.
@@ -30,59 +29,56 @@ export async function getAllEnvironments({
searchAggregatedTransactions: boolean;
includeMissing?: boolean;
}) {
- const spanName = serviceName
+ const operationName = serviceName
? 'get_all_environments_for_service'
: 'get_all_environments_for_all_services';
- return withApmSpan(spanName, async () => {
- const { apmEventClient, config } = setup;
- const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments'];
- // omit filter for service.name if "All" option is selected
- const serviceNameFilter = serviceName
- ? [{ term: { [SERVICE_NAME]: serviceName } }]
- : [];
+ const { apmEventClient, config } = setup;
+ const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments'];
- const params = {
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ProcessorEvent.error,
- ProcessorEvent.metric,
- ],
- },
- body: {
- // use timeout + min_doc_count to return as early as possible
- // if filter is not defined to prevent timeouts
- ...(!serviceName ? { timeout: '1ms' } : {}),
- size: 0,
- query: {
- bool: {
- filter: [...serviceNameFilter],
- },
+ // omit filter for service.name if "All" option is selected
+ const serviceNameFilter = serviceName
+ ? [{ term: { [SERVICE_NAME]: serviceName } }]
+ : [];
+
+ const params = {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ProcessorEvent.error,
+ ProcessorEvent.metric,
+ ],
+ },
+ body: {
+ // use timeout + min_doc_count to return as early as possible
+ // if filter is not defined to prevent timeouts
+ ...(!serviceName ? { timeout: '1ms' } : {}),
+ size: 0,
+ query: {
+ bool: {
+ filter: [...serviceNameFilter],
},
- aggs: {
- environments: {
- terms: {
- field: SERVICE_ENVIRONMENT,
- size: maxServiceEnvironments,
- ...(!serviceName ? { min_doc_count: 0 } : {}),
- missing: includeMissing
- ? ENVIRONMENT_NOT_DEFINED.value
- : undefined,
- },
+ },
+ aggs: {
+ environments: {
+ terms: {
+ field: SERVICE_ENVIRONMENT,
+ size: maxServiceEnvironments,
+ ...(!serviceName ? { min_doc_count: 0 } : {}),
+ missing: includeMissing ? ENVIRONMENT_NOT_DEFINED.value : undefined,
},
},
},
- };
+ },
+ };
- const resp = await apmEventClient.search(params);
+ const resp = await apmEventClient.search(operationName, params);
- const environments =
- resp.aggregations?.environments.buckets.map(
- (bucket) => bucket.key as string
- ) || [];
- return environments;
- });
+ const environments =
+ resp.aggregations?.environments.buckets.map(
+ (bucket) => bucket.key as string
+ ) || [];
+ return environments;
}
diff --git a/x-pack/plugins/apm/server/lib/environments/get_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_environments.ts
index 509e4cdcd67ac..c0b267f180010 100644
--- a/x-pack/plugins/apm/server/lib/environments/get_environments.ts
+++ b/x-pack/plugins/apm/server/lib/environments/get_environments.ts
@@ -12,7 +12,6 @@ import {
import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values';
import { ProcessorEvent } from '../../../common/processor_event';
import { rangeQuery } from '../../../server/utils/queries';
-import { withApmSpan } from '../../utils/with_apm_span';
import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
@@ -29,60 +28,58 @@ export async function getEnvironments({
serviceName?: string;
searchAggregatedTransactions: boolean;
}) {
- const spanName = serviceName
+ const operationName = serviceName
? 'get_environments_for_service'
: 'get_environments';
- return withApmSpan(spanName, async () => {
- const { start, end, apmEventClient, config } = setup;
+ const { start, end, apmEventClient, config } = setup;
- const filter = rangeQuery(start, end);
+ const filter = rangeQuery(start, end);
- if (serviceName) {
- filter.push({
- term: { [SERVICE_NAME]: serviceName },
- });
- }
+ if (serviceName) {
+ filter.push({
+ term: { [SERVICE_NAME]: serviceName },
+ });
+ }
- const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments'];
+ const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments'];
- const params = {
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ProcessorEvent.metric,
- ProcessorEvent.error,
- ],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter,
- },
+ const params = {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ProcessorEvent.metric,
+ ProcessorEvent.error,
+ ],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter,
},
- aggs: {
- environments: {
- terms: {
- field: SERVICE_ENVIRONMENT,
- missing: ENVIRONMENT_NOT_DEFINED.value,
- size: maxServiceEnvironments,
- },
+ },
+ aggs: {
+ environments: {
+ terms: {
+ field: SERVICE_ENVIRONMENT,
+ missing: ENVIRONMENT_NOT_DEFINED.value,
+ size: maxServiceEnvironments,
},
},
},
- };
+ },
+ };
- const resp = await apmEventClient.search(params);
- const aggs = resp.aggregations;
- const environmentsBuckets = aggs?.environments.buckets || [];
+ const resp = await apmEventClient.search(operationName, params);
+ const aggs = resp.aggregations;
+ const environmentsBuckets = aggs?.environments.buckets || [];
- const environments = environmentsBuckets.map(
- (environmentBucket) => environmentBucket.key as string
- );
+ const environments = environmentsBuckets.map(
+ (environmentBucket) => environmentBucket.key as string
+ );
- return environments;
- });
+ return environments;
}
diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/__snapshots__/get_buckets.test.ts.snap b/x-pack/plugins/apm/server/lib/errors/distribution/__snapshots__/get_buckets.test.ts.snap
index 43fe4dfe752e6..2c0330f17320d 100644
--- a/x-pack/plugins/apm/server/lib/errors/distribution/__snapshots__/get_buckets.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/errors/distribution/__snapshots__/get_buckets.test.ts.snap
@@ -3,6 +3,7 @@
exports[`get buckets should make the correct query 1`] = `
Array [
Array [
+ "get_error_distribution_buckets",
Object {
"apm": Object {
"events": Array [
diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts
index b1260d653f3de..712343d445d44 100644
--- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts
+++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts
@@ -65,7 +65,7 @@ describe('get buckets', () => {
});
it('should limit query results to error documents', () => {
- const query = clientSpy.mock.calls[0][0];
+ const query = clientSpy.mock.calls[0][1];
expect(query.apm.events).toEqual([ProcessorEvent.error]);
});
});
diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts
index 462c9bcdc4310..a51464764f2b4 100644
--- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts
+++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts
@@ -16,7 +16,6 @@ import {
rangeQuery,
kqlQuery,
} from '../../../../server/utils/queries';
-import { withApmSpan } from '../../../utils/with_apm_span';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
export async function getBuckets({
@@ -34,58 +33,59 @@ export async function getBuckets({
bucketSize: number;
setup: Setup & SetupTimeRange;
}) {
- return withApmSpan('get_error_distribution_buckets', async () => {
- const { start, end, apmEventClient } = setup;
- const filter: ESFilter[] = [
- { term: { [SERVICE_NAME]: serviceName } },
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ];
+ const { start, end, apmEventClient } = setup;
+ const filter: ESFilter[] = [
+ { term: { [SERVICE_NAME]: serviceName } },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ];
- if (groupId) {
- filter.push({ term: { [ERROR_GROUP_ID]: groupId } });
- }
+ if (groupId) {
+ filter.push({ term: { [ERROR_GROUP_ID]: groupId } });
+ }
- const params = {
- apm: {
- events: [ProcessorEvent.error],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter,
- },
+ const params = {
+ apm: {
+ events: [ProcessorEvent.error],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter,
},
- aggs: {
- distribution: {
- histogram: {
- field: '@timestamp',
- min_doc_count: 0,
- interval: bucketSize,
- extended_bounds: {
- min: start,
- max: end,
- },
+ },
+ aggs: {
+ distribution: {
+ histogram: {
+ field: '@timestamp',
+ min_doc_count: 0,
+ interval: bucketSize,
+ extended_bounds: {
+ min: start,
+ max: end,
},
},
},
},
- };
+ },
+ };
- const resp = await apmEventClient.search(params);
+ const resp = await apmEventClient.search(
+ 'get_error_distribution_buckets',
+ params
+ );
- const buckets = (resp.aggregations?.distribution.buckets || []).map(
- (bucket) => ({
- key: bucket.key,
- count: bucket.doc_count,
- })
- );
+ const buckets = (resp.aggregations?.distribution.buckets || []).map(
+ (bucket) => ({
+ key: bucket.key,
+ count: bucket.doc_count,
+ })
+ );
- return {
- noHits: resp.hits.total.value === 0,
- buckets: resp.hits.total.value > 0 ? buckets : [],
- };
- });
+ return {
+ noHits: resp.hits.total.value === 0,
+ buckets: resp.hits.total.value > 0 ? buckets : [],
+ };
}
diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts b/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts
index 57fb486180993..a915a4fb03305 100644
--- a/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts
+++ b/x-pack/plugins/apm/server/lib/errors/get_error_group_sample.ts
@@ -17,11 +17,10 @@ import {
rangeQuery,
kqlQuery,
} from '../../../server/utils/queries';
-import { withApmSpan } from '../../utils/with_apm_span';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
import { getTransaction } from '../transactions/get_transaction';
-export function getErrorGroupSample({
+export async function getErrorGroupSample({
environment,
kuery,
serviceName,
@@ -34,48 +33,46 @@ export function getErrorGroupSample({
groupId: string;
setup: Setup & SetupTimeRange;
}) {
- return withApmSpan('get_error_group_sample', async () => {
- const { start, end, apmEventClient } = setup;
+ const { start, end, apmEventClient } = setup;
- const params = {
- apm: {
- events: [ProcessorEvent.error as const],
- },
- body: {
- size: 1,
- query: {
- bool: {
- filter: [
- { term: { [SERVICE_NAME]: serviceName } },
- { term: { [ERROR_GROUP_ID]: groupId } },
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ],
- should: [{ term: { [TRANSACTION_SAMPLED]: true } }],
- },
+ const params = {
+ apm: {
+ events: [ProcessorEvent.error as const],
+ },
+ body: {
+ size: 1,
+ query: {
+ bool: {
+ filter: [
+ { term: { [SERVICE_NAME]: serviceName } },
+ { term: { [ERROR_GROUP_ID]: groupId } },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ],
+ should: [{ term: { [TRANSACTION_SAMPLED]: true } }],
},
- sort: asMutableArray([
- { _score: 'desc' }, // sort by _score first to ensure that errors with transaction.sampled:true ends up on top
- { '@timestamp': { order: 'desc' } }, // sort by timestamp to get the most recent error
- ] as const),
},
- };
+ sort: asMutableArray([
+ { _score: 'desc' }, // sort by _score first to ensure that errors with transaction.sampled:true ends up on top
+ { '@timestamp': { order: 'desc' } }, // sort by timestamp to get the most recent error
+ ] as const),
+ },
+ };
- const resp = await apmEventClient.search(params);
- const error = resp.hits.hits[0]?._source;
- const transactionId = error?.transaction?.id;
- const traceId = error?.trace?.id;
+ const resp = await apmEventClient.search('get_error_group_sample', params);
+ const error = resp.hits.hits[0]?._source;
+ const transactionId = error?.transaction?.id;
+ const traceId = error?.trace?.id;
- let transaction;
- if (transactionId && traceId) {
- transaction = await getTransaction({ transactionId, traceId, setup });
- }
+ let transaction;
+ if (transactionId && traceId) {
+ transaction = await getTransaction({ transactionId, traceId, setup });
+ }
- return {
- transaction,
- error,
- occurrencesCount: resp.hits.total.value,
- };
- });
+ return {
+ transaction,
+ error,
+ occurrencesCount: resp.hits.total.value,
+ };
}
diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts b/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts
index f5b22e5349756..d705a2eb5a00c 100644
--- a/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts
+++ b/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts
@@ -15,11 +15,10 @@ import {
} from '../../../common/elasticsearch_fieldnames';
import { getErrorGroupsProjection } from '../../projections/errors';
import { mergeProjection } from '../../projections/util/merge_projection';
-import { withApmSpan } from '../../utils/with_apm_span';
import { getErrorName } from '../helpers/get_error_name';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
-export function getErrorGroups({
+export async function getErrorGroups({
environment,
kuery,
serviceName,
@@ -34,87 +33,83 @@ export function getErrorGroups({
sortDirection?: 'asc' | 'desc';
setup: Setup & SetupTimeRange;
}) {
- return withApmSpan('get_error_groups', async () => {
- const { apmEventClient } = setup;
+ const { apmEventClient } = setup;
- // sort buckets by last occurrence of error
- const sortByLatestOccurrence = sortField === 'latestOccurrenceAt';
+ // sort buckets by last occurrence of error
+ const sortByLatestOccurrence = sortField === 'latestOccurrenceAt';
- const projection = getErrorGroupsProjection({
- environment,
- kuery,
- setup,
- serviceName,
- });
+ const projection = getErrorGroupsProjection({
+ environment,
+ kuery,
+ setup,
+ serviceName,
+ });
- const order = sortByLatestOccurrence
- ? {
- max_timestamp: sortDirection,
- }
- : { _count: sortDirection };
+ const order = sortByLatestOccurrence
+ ? {
+ max_timestamp: sortDirection,
+ }
+ : { _count: sortDirection };
- const params = mergeProjection(projection, {
- body: {
- size: 0,
- aggs: {
- error_groups: {
- terms: {
- ...projection.body.aggs.error_groups.terms,
- size: 500,
- order,
- },
- aggs: {
- sample: {
- top_hits: {
- _source: [
- ERROR_LOG_MESSAGE,
- ERROR_EXC_MESSAGE,
- ERROR_EXC_HANDLED,
- ERROR_EXC_TYPE,
- ERROR_CULPRIT,
- ERROR_GROUP_ID,
- '@timestamp',
- ],
- sort: [{ '@timestamp': 'desc' as const }],
- size: 1,
- },
+ const params = mergeProjection(projection, {
+ body: {
+ size: 0,
+ aggs: {
+ error_groups: {
+ terms: {
+ ...projection.body.aggs.error_groups.terms,
+ size: 500,
+ order,
+ },
+ aggs: {
+ sample: {
+ top_hits: {
+ _source: [
+ ERROR_LOG_MESSAGE,
+ ERROR_EXC_MESSAGE,
+ ERROR_EXC_HANDLED,
+ ERROR_EXC_TYPE,
+ ERROR_CULPRIT,
+ ERROR_GROUP_ID,
+ '@timestamp',
+ ],
+ sort: [{ '@timestamp': 'desc' as const }],
+ size: 1,
},
- ...(sortByLatestOccurrence
- ? {
- max_timestamp: {
- max: {
- field: '@timestamp',
- },
- },
- }
- : {}),
},
+ ...(sortByLatestOccurrence
+ ? {
+ max_timestamp: {
+ max: {
+ field: '@timestamp',
+ },
+ },
+ }
+ : {}),
},
},
},
- });
+ },
+ });
- const resp = await apmEventClient.search(params);
+ const resp = await apmEventClient.search('get_error_groups', params);
- // aggregations can be undefined when no matching indices are found.
- // this is an exception rather than the rule so the ES type does not account for this.
- const hits = (resp.aggregations?.error_groups.buckets || []).map(
- (bucket) => {
- const source = bucket.sample.hits.hits[0]._source;
- const message = getErrorName(source);
+ // aggregations can be undefined when no matching indices are found.
+ // this is an exception rather than the rule so the ES type does not account for this.
+ const hits = (resp.aggregations?.error_groups.buckets || []).map((bucket) => {
+ const source = bucket.sample.hits.hits[0]._source;
+ const message = getErrorName(source);
- return {
- message,
- occurrenceCount: bucket.doc_count,
- culprit: source.error.culprit,
- groupId: source.error.grouping_key,
- latestOccurrenceAt: source['@timestamp'],
- handled: source.error.exception?.[0].handled,
- type: source.error.exception?.[0].type,
- };
- }
- );
-
- return hits;
+ return {
+ message,
+ occurrenceCount: bucket.doc_count,
+ culprit: source.error.culprit,
+ groupId: source.error.grouping_key,
+ latestOccurrenceAt: source['@timestamp'],
+ handled: source.error.exception?.[0].handled,
+ type: source.error.exception?.[0].type,
+ };
});
+
+ return hits;
}
diff --git a/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts b/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts
index 394cf6b988f12..8bfb137c1689c 100644
--- a/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts
@@ -14,7 +14,6 @@ import {
} from '../../../../common/elasticsearch_fieldnames';
import { APMConfig } from '../../..';
import { APMEventClient } from '../create_es_client/create_apm_event_client';
-import { withApmSpan } from '../../../utils/with_apm_span';
export async function getHasAggregatedTransactions({
start,
@@ -25,8 +24,9 @@ export async function getHasAggregatedTransactions({
end?: number;
apmEventClient: APMEventClient;
}) {
- return withApmSpan('get_has_aggregated_transactions', async () => {
- const response = await apmEventClient.search({
+ const response = await apmEventClient.search(
+ 'get_has_aggregated_transactions',
+ {
apm: {
events: [ProcessorEvent.metric],
},
@@ -41,14 +41,14 @@ export async function getHasAggregatedTransactions({
},
},
terminateAfter: 1,
- });
-
- if (response.hits.total.value > 0) {
- return true;
}
+ );
+
+ if (response.hits.total.value > 0) {
+ return true;
+ }
- return false;
- });
+ return false;
}
export async function getSearchAggregatedTransactions({
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
index 989297544c78f..39018e26f371c 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
@@ -81,17 +81,26 @@ export async function callAsyncWithDebug({
return res;
}
-export const getDebugBody = (
- params: Record,
- requestType: string
-) => {
+export const getDebugBody = ({
+ params,
+ requestType,
+ operationName,
+}: {
+ params: Record;
+ requestType: string;
+ operationName: string;
+}) => {
+ const operationLine = `${operationName}\n`;
+
if (requestType === 'search') {
- return `GET ${params.index}/_search\n${formatObj(params.body)}`;
+ return `${operationLine}GET ${params.index}/_search\n${formatObj(
+ params.body
+ )}`;
}
return `${chalk.bold('ES operation:')} ${requestType}\n${chalk.bold(
'ES query:'
- )}\n${formatObj(params)}`;
+ )}\n${operationLine}${formatObj(params)}`;
};
export const getDebugTitle = (request: KibanaRequest) =>
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts
index addd7391d782d..8e82a189d75f3 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts
@@ -47,7 +47,7 @@ describe('createApmEventClient', () => {
},
});
- await eventClient.search({
+ await eventClient.search('foo', {
apm: {
events: [],
},
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
index b8a14253a229a..916a6981f286a 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
@@ -6,6 +6,7 @@
*/
import { ValuesType } from 'utility-types';
+import { withApmSpan } from '../../../../utils/with_apm_span';
import { Profile } from '../../../../../typings/es_schemas/ui/profile';
import {
ElasticsearchClient,
@@ -34,6 +35,7 @@ import { unpackProcessorEvents } from './unpack_processor_events';
export type APMEventESSearchRequest = Omit & {
apm: {
events: ProcessorEvent[];
+ includeLegacyData?: boolean;
};
};
@@ -78,11 +80,13 @@ export function createApmEventClient({
}) {
return {
async search(
- params: TParams,
- { includeLegacyData = false } = {}
+ operationName: string,
+ params: TParams
): Promise> {
const withProcessorEventFilter = unpackProcessorEvents(params, indices);
+ const { includeLegacyData = false } = params.apm;
+
const withPossibleLegacyDataFilter = !includeLegacyData
? addFilterToExcludeLegacyData(withProcessorEventFilter)
: withProcessorEventFilter;
@@ -98,15 +102,18 @@ export function createApmEventClient({
return callAsyncWithDebug({
cb: () => {
- const searchPromise = cancelEsRequestOnAbort(
- esClient.search(searchParams),
- request
+ const searchPromise = withApmSpan(operationName, () =>
+ cancelEsRequestOnAbort(esClient.search(searchParams), request)
);
return unwrapEsResponse(searchPromise);
},
getDebugMessage: () => ({
- body: getDebugBody(searchParams, requestType),
+ body: getDebugBody({
+ params: searchParams,
+ requestType,
+ operationName,
+ }),
title: getDebugTitle(request),
}),
isCalledWithInternalUser: false,
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
index 1544538de74a6..e6b61a709ae35 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
@@ -31,20 +31,23 @@ export function createInternalESClient({
}: Pick & { debug: boolean }) {
const { asInternalUser } = context.core.elasticsearch.client;
- function callEs({
- cb,
- requestType,
- params,
- }: {
- requestType: string;
- cb: () => TransportRequestPromise;
- params: Record;
- }) {
+ function callEs(
+ operationName: string,
+ {
+ cb,
+ requestType,
+ params,
+ }: {
+ requestType: string;
+ cb: () => TransportRequestPromise;
+ params: Record;
+ }
+ ) {
return callAsyncWithDebug({
cb: () => unwrapEsResponse(cancelEsRequestOnAbort(cb(), request)),
getDebugMessage: () => ({
title: getDebugTitle(request),
- body: getDebugBody(params, requestType),
+ body: getDebugBody({ params, requestType, operationName }),
}),
debug,
isCalledWithInternalUser: true,
@@ -59,30 +62,37 @@ export function createInternalESClient({
TDocument = unknown,
TSearchRequest extends ESSearchRequest = ESSearchRequest
>(
+ operationName: string,
params: TSearchRequest
): Promise> => {
- return callEs({
+ return callEs(operationName, {
requestType: 'search',
cb: () => asInternalUser.search(params),
params,
});
},
- index: (params: APMIndexDocumentParams) => {
- return callEs({
+ index: (operationName: string, params: APMIndexDocumentParams) => {
+ return callEs(operationName, {
requestType: 'index',
cb: () => asInternalUser.index(params),
params,
});
},
- delete: (params: estypes.DeleteRequest): Promise<{ result: string }> => {
- return callEs({
+ delete: (
+ operationName: string,
+ params: estypes.DeleteRequest
+ ): Promise<{ result: string }> => {
+ return callEs(operationName, {
requestType: 'delete',
cb: () => asInternalUser.delete(params),
params,
});
},
- indicesCreate: (params: estypes.IndicesCreateRequest) => {
- return callEs({
+ indicesCreate: (
+ operationName: string,
+ params: estypes.IndicesCreateRequest
+ ) => {
+ return callEs(operationName, {
requestType: 'indices.create',
cb: () => asInternalUser.indices.create(params),
params,
diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
index c0ff0cab88f47..66b3c91fc6f2d 100644
--- a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
@@ -109,7 +109,7 @@ describe('setupRequest', () => {
it('calls callWithRequest', async () => {
const mockResources = getMockResources();
const { apmEventClient } = await setupRequest(mockResources);
- await apmEventClient.search({
+ await apmEventClient.search('foo', {
apm: { events: [ProcessorEvent.transaction] },
body: { foo: 'bar' },
});
@@ -137,7 +137,7 @@ describe('setupRequest', () => {
it('calls callWithInternalUser', async () => {
const mockResources = getMockResources();
const { internalClient } = await setupRequest(mockResources);
- await internalClient.search({
+ await internalClient.search('foo', {
index: ['apm-*'],
body: { foo: 'bar' },
} as any);
@@ -156,7 +156,7 @@ describe('setupRequest', () => {
it('adds a range filter for `observer.version_major` to the existing filter', async () => {
const mockResources = getMockResources();
const { apmEventClient } = await setupRequest(mockResources);
- await apmEventClient.search({
+ await apmEventClient.search('foo', {
apm: {
events: [ProcessorEvent.transaction],
},
@@ -183,19 +183,15 @@ describe('setupRequest', () => {
it('does not add a range filter for `observer.version_major` if includeLegacyData=true', async () => {
const mockResources = getMockResources();
const { apmEventClient } = await setupRequest(mockResources);
- await apmEventClient.search(
- {
- apm: {
- events: [ProcessorEvent.error],
- },
- body: {
- query: { bool: { filter: [{ term: { field: 'someTerm' } }] } },
- },
- },
- {
+ await apmEventClient.search('foo', {
+ apm: {
+ events: [ProcessorEvent.error],
includeLegacyData: true,
- }
- );
+ },
+ body: {
+ query: { bool: { filter: [{ term: { field: 'someTerm' } }] } },
+ },
+ });
const params =
mockResources.context.core.elasticsearch.client.asCurrentUser.search
.mock.calls[0][0];
@@ -221,7 +217,7 @@ describe('without a bool filter', () => {
it('adds a range filter for `observer.version_major`', async () => {
const mockResources = getMockResources();
const { apmEventClient } = await setupRequest(mockResources);
- await apmEventClient.search({
+ await apmEventClient.search('foo', {
apm: {
events: [ProcessorEvent.error],
},
@@ -251,7 +247,7 @@ describe('with includeFrozen=false', () => {
const { apmEventClient } = await setupRequest(mockResources);
- await apmEventClient.search({
+ await apmEventClient.search('foo', {
apm: {
events: [],
},
@@ -273,7 +269,7 @@ describe('with includeFrozen=true', () => {
const { apmEventClient } = await setupRequest(mockResources);
- await apmEventClient.search({
+ await apmEventClient.search('foo', {
apm: { events: [] },
});
diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts
index 3b3ef8b9c4bcf..d1040b49dcd8b 100644
--- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts
+++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts
@@ -30,6 +30,7 @@ export async function fetchAndTransformGcMetrics({
serviceNodeName,
chartBase,
fieldName,
+ operationName,
}: {
environment?: string;
kuery?: string;
@@ -38,6 +39,7 @@ export async function fetchAndTransformGcMetrics({
serviceNodeName?: string;
chartBase: ChartBase;
fieldName: typeof METRIC_JAVA_GC_COUNT | typeof METRIC_JAVA_GC_TIME;
+ operationName: string;
}) {
const { start, end, apmEventClient, config } = setup;
@@ -108,7 +110,7 @@ export async function fetchAndTransformGcMetrics({
},
});
- const response = await apmEventClient.search(params);
+ const response = await apmEventClient.search(operationName, params);
const { aggregations } = response;
diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts
index 388331f3bbf17..3ec40d5171694 100644
--- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts
+++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts
@@ -7,7 +7,6 @@
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
-import { withApmSpan } from '../../../../../utils/with_apm_span';
import { METRIC_JAVA_GC_COUNT } from '../../../../../../common/elasticsearch_fieldnames';
import { Setup, SetupTimeRange } from '../../../../helpers/setup_request';
import { fetchAndTransformGcMetrics } from './fetch_and_transform_gc_metrics';
@@ -45,17 +44,16 @@ function getGcRateChart({
serviceName: string;
serviceNodeName?: string;
}) {
- return withApmSpan('get_gc_rate_charts', () =>
- fetchAndTransformGcMetrics({
- environment,
- kuery,
- setup,
- serviceName,
- serviceNodeName,
- chartBase,
- fieldName: METRIC_JAVA_GC_COUNT,
- })
- );
+ return fetchAndTransformGcMetrics({
+ environment,
+ kuery,
+ setup,
+ serviceName,
+ serviceNodeName,
+ chartBase,
+ fieldName: METRIC_JAVA_GC_COUNT,
+ operationName: 'get_gc_rate_charts',
+ });
}
export { getGcRateChart };
diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts
index e6f80190d1daa..8e4416d94fb90 100644
--- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts
+++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts
@@ -7,7 +7,6 @@
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
-import { withApmSpan } from '../../../../../utils/with_apm_span';
import { METRIC_JAVA_GC_TIME } from '../../../../../../common/elasticsearch_fieldnames';
import { Setup, SetupTimeRange } from '../../../../helpers/setup_request';
import { fetchAndTransformGcMetrics } from './fetch_and_transform_gc_metrics';
@@ -45,17 +44,16 @@ function getGcTimeChart({
serviceName: string;
serviceNodeName?: string;
}) {
- return withApmSpan('get_gc_time_charts', () =>
- fetchAndTransformGcMetrics({
- environment,
- kuery,
- setup,
- serviceName,
- serviceNodeName,
- chartBase,
- fieldName: METRIC_JAVA_GC_TIME,
- })
- );
+ return fetchAndTransformGcMetrics({
+ environment,
+ kuery,
+ setup,
+ serviceName,
+ serviceNodeName,
+ chartBase,
+ fieldName: METRIC_JAVA_GC_TIME,
+ operationName: 'get_gc_time_charts',
+ });
}
export { getGcTimeChart };
diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts
index 7630827a3cb38..6a23213e94537 100644
--- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts
+++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts
@@ -7,7 +7,6 @@
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
-import { withApmSpan } from '../../../../../utils/with_apm_span';
import {
METRIC_JAVA_HEAP_MEMORY_MAX,
METRIC_JAVA_HEAP_MEMORY_COMMITTED,
@@ -65,22 +64,21 @@ export function getHeapMemoryChart({
serviceName: string;
serviceNodeName?: string;
}) {
- return withApmSpan('get_heap_memory_charts', () =>
- fetchAndTransformMetrics({
- environment,
- kuery,
- setup,
- serviceName,
- serviceNodeName,
- chartBase,
- aggs: {
- heapMemoryMax: { avg: { field: METRIC_JAVA_HEAP_MEMORY_MAX } },
- heapMemoryCommitted: {
- avg: { field: METRIC_JAVA_HEAP_MEMORY_COMMITTED },
- },
- heapMemoryUsed: { avg: { field: METRIC_JAVA_HEAP_MEMORY_USED } },
+ return fetchAndTransformMetrics({
+ environment,
+ kuery,
+ setup,
+ serviceName,
+ serviceNodeName,
+ chartBase,
+ aggs: {
+ heapMemoryMax: { avg: { field: METRIC_JAVA_HEAP_MEMORY_MAX } },
+ heapMemoryCommitted: {
+ avg: { field: METRIC_JAVA_HEAP_MEMORY_COMMITTED },
},
- additionalFilters: [{ term: { [AGENT_NAME]: 'java' } }],
- })
- );
+ heapMemoryUsed: { avg: { field: METRIC_JAVA_HEAP_MEMORY_USED } },
+ },
+ additionalFilters: [{ term: { [AGENT_NAME]: 'java' } }],
+ operationName: 'get_heap_memory_charts',
+ });
}
diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts
index cd11e5e5383b6..1ceb42b7db479 100644
--- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts
+++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts
@@ -7,7 +7,6 @@
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
-import { withApmSpan } from '../../../../../utils/with_apm_span';
import {
METRIC_JAVA_NON_HEAP_MEMORY_MAX,
METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED,
@@ -62,24 +61,23 @@ export async function getNonHeapMemoryChart({
serviceName: string;
serviceNodeName?: string;
}) {
- return withApmSpan('get_non_heap_memory_charts', () =>
- fetchAndTransformMetrics({
- environment,
- kuery,
- setup,
- serviceName,
- serviceNodeName,
- chartBase,
- aggs: {
- nonHeapMemoryMax: { avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_MAX } },
- nonHeapMemoryCommitted: {
- avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED },
- },
- nonHeapMemoryUsed: {
- avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_USED },
- },
+ return fetchAndTransformMetrics({
+ environment,
+ kuery,
+ setup,
+ serviceName,
+ serviceNodeName,
+ chartBase,
+ aggs: {
+ nonHeapMemoryMax: { avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_MAX } },
+ nonHeapMemoryCommitted: {
+ avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED },
},
- additionalFilters: [{ term: { [AGENT_NAME]: 'java' } }],
- })
- );
+ nonHeapMemoryUsed: {
+ avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_USED },
+ },
+ },
+ additionalFilters: [{ term: { [AGENT_NAME]: 'java' } }],
+ operationName: 'get_non_heap_memory_charts',
+ });
}
diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts
index 8d4c079197d19..700c5e08d4def 100644
--- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts
+++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts
@@ -7,7 +7,6 @@
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
-import { withApmSpan } from '../../../../../utils/with_apm_span';
import {
METRIC_JAVA_THREAD_COUNT,
AGENT_NAME,
@@ -54,19 +53,18 @@ export async function getThreadCountChart({
serviceName: string;
serviceNodeName?: string;
}) {
- return withApmSpan('get_thread_count_charts', () =>
- fetchAndTransformMetrics({
- environment,
- kuery,
- setup,
- serviceName,
- serviceNodeName,
- chartBase,
- aggs: {
- threadCount: { avg: { field: METRIC_JAVA_THREAD_COUNT } },
- threadCountMax: { max: { field: METRIC_JAVA_THREAD_COUNT } },
- },
- additionalFilters: [{ term: { [AGENT_NAME]: 'java' } }],
- })
- );
+ return fetchAndTransformMetrics({
+ environment,
+ kuery,
+ setup,
+ serviceName,
+ serviceNodeName,
+ chartBase,
+ aggs: {
+ threadCount: { avg: { field: METRIC_JAVA_THREAD_COUNT } },
+ threadCountMax: { max: { field: METRIC_JAVA_THREAD_COUNT } },
+ },
+ additionalFilters: [{ term: { [AGENT_NAME]: 'java' } }],
+ operationName: 'get_thread_count_charts',
+ });
}
diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts
index 37bef191ae876..a568d58bdd438 100644
--- a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts
+++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts
@@ -7,7 +7,6 @@
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
-import { withApmSpan } from '../../../../../utils/with_apm_span';
import {
METRIC_SYSTEM_CPU_PERCENT,
METRIC_PROCESS_CPU_PERCENT,
@@ -66,20 +65,19 @@ export function getCPUChartData({
serviceName: string;
serviceNodeName?: string;
}) {
- return withApmSpan('get_cpu_metric_charts', () =>
- fetchAndTransformMetrics({
- environment,
- kuery,
- setup,
- serviceName,
- serviceNodeName,
- chartBase,
- aggs: {
- systemCPUAverage: { avg: { field: METRIC_SYSTEM_CPU_PERCENT } },
- systemCPUMax: { max: { field: METRIC_SYSTEM_CPU_PERCENT } },
- processCPUAverage: { avg: { field: METRIC_PROCESS_CPU_PERCENT } },
- processCPUMax: { max: { field: METRIC_PROCESS_CPU_PERCENT } },
- },
- })
- );
+ return fetchAndTransformMetrics({
+ environment,
+ kuery,
+ setup,
+ serviceName,
+ serviceNodeName,
+ chartBase,
+ aggs: {
+ systemCPUAverage: { avg: { field: METRIC_SYSTEM_CPU_PERCENT } },
+ systemCPUMax: { max: { field: METRIC_SYSTEM_CPU_PERCENT } },
+ processCPUAverage: { avg: { field: METRIC_PROCESS_CPU_PERCENT } },
+ processCPUMax: { max: { field: METRIC_PROCESS_CPU_PERCENT } },
+ },
+ operationName: 'get_cpu_metric_charts',
+ });
}
diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts
index 0ec2f2c2fcfb2..1f7860d567b03 100644
--- a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts
+++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts
@@ -84,45 +84,41 @@ export async function getMemoryChartData({
serviceNodeName?: string;
}) {
return withApmSpan('get_memory_metrics_charts', async () => {
- const cgroupResponse = await withApmSpan(
- 'get_cgroup_memory_metrics_charts',
- () =>
- fetchAndTransformMetrics({
- environment,
- kuery,
- setup,
- serviceName,
- serviceNodeName,
- chartBase,
- aggs: {
- memoryUsedAvg: { avg: { script: percentCgroupMemoryUsedScript } },
- memoryUsedMax: { max: { script: percentCgroupMemoryUsedScript } },
- },
- additionalFilters: [
- { exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES } },
- ],
- })
- );
+ const cgroupResponse = await fetchAndTransformMetrics({
+ environment,
+ kuery,
+ setup,
+ serviceName,
+ serviceNodeName,
+ chartBase,
+ aggs: {
+ memoryUsedAvg: { avg: { script: percentCgroupMemoryUsedScript } },
+ memoryUsedMax: { max: { script: percentCgroupMemoryUsedScript } },
+ },
+ additionalFilters: [
+ { exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES } },
+ ],
+ operationName: 'get_cgroup_memory_metrics_charts',
+ });
if (cgroupResponse.noHits) {
- return await withApmSpan('get_system_memory_metrics_charts', () =>
- fetchAndTransformMetrics({
- environment,
- kuery,
- setup,
- serviceName,
- serviceNodeName,
- chartBase,
- aggs: {
- memoryUsedAvg: { avg: { script: percentSystemMemoryUsedScript } },
- memoryUsedMax: { max: { script: percentSystemMemoryUsedScript } },
- },
- additionalFilters: [
- { exists: { field: METRIC_SYSTEM_FREE_MEMORY } },
- { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
- ],
- })
- );
+ return await fetchAndTransformMetrics({
+ environment,
+ kuery,
+ setup,
+ serviceName,
+ serviceNodeName,
+ chartBase,
+ aggs: {
+ memoryUsedAvg: { avg: { script: percentSystemMemoryUsedScript } },
+ memoryUsedMax: { max: { script: percentSystemMemoryUsedScript } },
+ },
+ additionalFilters: [
+ { exists: { field: METRIC_SYSTEM_FREE_MEMORY } },
+ { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
+ ],
+ operationName: 'get_system_memory_metrics_charts',
+ });
}
return cgroupResponse;
diff --git a/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts b/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts
index 30234447821ec..df9e33e6f4b40 100644
--- a/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts
+++ b/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts
@@ -56,6 +56,7 @@ export async function fetchAndTransformMetrics({
chartBase,
aggs,
additionalFilters = [],
+ operationName,
}: {
environment?: string;
kuery?: string;
@@ -65,6 +66,7 @@ export async function fetchAndTransformMetrics({
chartBase: ChartBase;
aggs: T;
additionalFilters?: Filter[];
+ operationName: string;
}) {
const { start, end, apmEventClient, config } = setup;
@@ -98,7 +100,7 @@ export async function fetchAndTransformMetrics({
},
});
- const response = await apmEventClient.search(params);
+ const response = await apmEventClient.search(operationName, params);
return transformDataToMetricsChart(response, chartBase);
}
diff --git a/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts b/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts
index 2ccbe318862f1..086516371387e 100644
--- a/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts
+++ b/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts
@@ -10,40 +10,40 @@ import { rangeQuery } from '../../../server/utils/queries';
import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions';
-import { withApmSpan } from '../../utils/with_apm_span';
-export function getServiceCount({
+export async function getServiceCount({
setup,
searchAggregatedTransactions,
}: {
setup: Setup & SetupTimeRange;
searchAggregatedTransactions: boolean;
}) {
- return withApmSpan('observability_overview_get_service_count', async () => {
- const { apmEventClient, start, end } = setup;
+ const { apmEventClient, start, end } = setup;
- const params = {
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ProcessorEvent.error,
- ProcessorEvent.metric,
- ],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter: rangeQuery(start, end),
- },
+ const params = {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ProcessorEvent.error,
+ ProcessorEvent.metric,
+ ],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: rangeQuery(start, end),
},
- aggs: { serviceCount: { cardinality: { field: SERVICE_NAME } } },
},
- };
+ aggs: { serviceCount: { cardinality: { field: SERVICE_NAME } } },
+ },
+ };
- const { aggregations } = await apmEventClient.search(params);
- return aggregations?.serviceCount.value || 0;
- });
+ const { aggregations } = await apmEventClient.search(
+ 'observability_overview_get_service_count',
+ params
+ );
+ return aggregations?.serviceCount.value || 0;
}
diff --git a/x-pack/plugins/apm/server/lib/observability_overview/get_transactions_per_minute.ts b/x-pack/plugins/apm/server/lib/observability_overview/get_transactions_per_minute.ts
index da8ac7c50b594..016cb50566da0 100644
--- a/x-pack/plugins/apm/server/lib/observability_overview/get_transactions_per_minute.ts
+++ b/x-pack/plugins/apm/server/lib/observability_overview/get_transactions_per_minute.ts
@@ -14,9 +14,8 @@ import { rangeQuery } from '../../../server/utils/queries';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions';
import { calculateThroughput } from '../helpers/calculate_throughput';
-import { withApmSpan } from '../../utils/with_apm_span';
-export function getTransactionsPerMinute({
+export async function getTransactionsPerMinute({
setup,
bucketSize,
searchAggregatedTransactions,
@@ -25,71 +24,69 @@ export function getTransactionsPerMinute({
bucketSize: string;
searchAggregatedTransactions: boolean;
}) {
- return withApmSpan(
- 'observability_overview_get_transactions_per_minute',
- async () => {
- const { apmEventClient, start, end } = setup;
+ const { apmEventClient, start, end } = setup;
- const { aggregations } = await apmEventClient.search({
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ],
+ const { aggregations } = await apmEventClient.search(
+ 'observability_overview_get_transactions_per_minute',
+ {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: rangeQuery(start, end),
+ },
},
- body: {
- size: 0,
- query: {
- bool: {
- filter: rangeQuery(start, end),
+ aggs: {
+ transactionType: {
+ terms: {
+ field: TRANSACTION_TYPE,
},
- },
- aggs: {
- transactionType: {
- terms: {
- field: TRANSACTION_TYPE,
- },
- aggs: {
- timeseries: {
- date_histogram: {
- field: '@timestamp',
- fixed_interval: bucketSize,
- min_doc_count: 0,
- },
- aggs: {
- throughput: { rate: { unit: 'minute' as const } },
- },
+ aggs: {
+ timeseries: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: bucketSize,
+ min_doc_count: 0,
+ },
+ aggs: {
+ throughput: { rate: { unit: 'minute' as const } },
},
},
},
},
},
- });
+ },
+ }
+ );
- if (!aggregations || !aggregations.transactionType.buckets) {
- return { value: undefined, timeseries: [] };
- }
+ if (!aggregations || !aggregations.transactionType.buckets) {
+ return { value: undefined, timeseries: [] };
+ }
- const topTransactionTypeBucket =
- aggregations.transactionType.buckets.find(
- ({ key: transactionType }) =>
- transactionType === TRANSACTION_REQUEST ||
- transactionType === TRANSACTION_PAGE_LOAD
- ) || aggregations.transactionType.buckets[0];
+ const topTransactionTypeBucket =
+ aggregations.transactionType.buckets.find(
+ ({ key: transactionType }) =>
+ transactionType === TRANSACTION_REQUEST ||
+ transactionType === TRANSACTION_PAGE_LOAD
+ ) || aggregations.transactionType.buckets[0];
- return {
- value: calculateThroughput({
- start,
- end,
- value: topTransactionTypeBucket?.doc_count || 0,
- }),
- timeseries:
- topTransactionTypeBucket?.timeseries.buckets.map((bucket) => ({
- x: bucket.key,
- y: bucket.throughput.value,
- })) || [],
- };
- }
- );
+ return {
+ value: calculateThroughput({
+ start,
+ end,
+ value: topTransactionTypeBucket?.doc_count || 0,
+ }),
+ timeseries:
+ topTransactionTypeBucket?.timeseries.buckets.map((bucket) => ({
+ x: bucket.key,
+ y: bucket.throughput.value,
+ })) || [],
+ };
}
diff --git a/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts b/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts
index bbe13874d7d3b..5c1a33e750e12 100644
--- a/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts
+++ b/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts
@@ -6,31 +6,31 @@
*/
import { ProcessorEvent } from '../../../common/processor_event';
-import { withApmSpan } from '../../utils/with_apm_span';
import { Setup } from '../helpers/setup_request';
-export function getHasData({ setup }: { setup: Setup }) {
- return withApmSpan('observability_overview_has_apm_data', async () => {
- const { apmEventClient } = setup;
- try {
- const params = {
- apm: {
- events: [
- ProcessorEvent.transaction,
- ProcessorEvent.error,
- ProcessorEvent.metric,
- ],
- },
- terminateAfter: 1,
- body: {
- size: 0,
- },
- };
+export async function getHasData({ setup }: { setup: Setup }) {
+ const { apmEventClient } = setup;
+ try {
+ const params = {
+ apm: {
+ events: [
+ ProcessorEvent.transaction,
+ ProcessorEvent.error,
+ ProcessorEvent.metric,
+ ],
+ },
+ terminateAfter: 1,
+ body: {
+ size: 0,
+ },
+ };
- const response = await apmEventClient.search(params);
- return response.hits.total.value > 0;
- } catch (e) {
- return false;
- }
- });
+ const response = await apmEventClient.search(
+ 'observability_overview_has_apm_data',
+ params
+ );
+ return response.hits.total.value > 0;
+ } catch (e) {
+ return false;
+ }
}
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts b/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts
index ea1a65020e0cc..e56f234c0633e 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts
@@ -63,7 +63,7 @@ export async function getClientMetrics({
});
const { apmEventClient } = setup;
- const response = await apmEventClient.search(params);
+ const response = await apmEventClient.search('get_client_metrics', params);
const {
hasFetchStartField: { backEnd, totalPageLoadDuration },
} = response.aggregations!;
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_js_errors.ts b/x-pack/plugins/apm/server/lib/rum_client/get_js_errors.ts
index d65399f91bef8..6f734a214501d 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_js_errors.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_js_errors.ts
@@ -94,7 +94,7 @@ export async function getJSErrors({
const { apmEventClient } = setup;
- const response = await apmEventClient.search(params);
+ const response = await apmEventClient.search('get_js_errors', params);
const { totalErrorGroups, totalErrorPages, errors } =
response.aggregations ?? {};
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts b/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts
index c873b9b4aed82..c4c6f613172d1 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_long_task_metrics.ts
@@ -64,7 +64,7 @@ export async function getLongTaskMetrics({
const { apmEventClient } = setup;
- const response = await apmEventClient.search(params);
+ const response = await apmEventClient.search('get_long_task_metrics', params);
const pkey = percentile.toFixed(1);
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts
index 0f1d7146f8459..73d634e3134d1 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_load_distribution.ts
@@ -117,7 +117,7 @@ export async function getPageLoadDistribution({
const {
aggregations,
hits: { total },
- } = await apmEventClient.search(params);
+ } = await apmEventClient.search('get_page_load_distribution', params);
if (total.value === 0) {
return null;
@@ -210,7 +210,10 @@ const getPercentilesDistribution = async ({
const { apmEventClient } = setup;
- const { aggregations } = await apmEventClient.search(params);
+ const { aggregations } = await apmEventClient.search(
+ 'get_page_load_distribution',
+ params
+ );
return aggregations?.loadDistribution.values ?? [];
};
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts
index 13046e6c5a873..41af2ae166aaf 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts
@@ -69,7 +69,7 @@ export async function getPageViewTrends({
const { apmEventClient } = setup;
- const response = await apmEventClient.search(params);
+ const response = await apmEventClient.search('get_page_view_trends', params);
const { topBreakdowns } = response.aggregations ?? {};
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts b/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts
index 6a6caab953733..e63d834307a5f 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_pl_dist_breakdown.ts
@@ -92,7 +92,10 @@ export const getPageLoadDistBreakdown = async ({
const { apmEventClient } = setup;
- const { aggregations } = await apmEventClient.search(params);
+ const { aggregations } = await apmEventClient.search(
+ 'get_page_load_dist_breakdown',
+ params
+ );
const pageDistBreakdowns = aggregations?.breakdowns.buckets;
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_rum_services.ts b/x-pack/plugins/apm/server/lib/rum_client/get_rum_services.ts
index ffe9225f1ab99..a2e6b55738d3a 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_rum_services.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_rum_services.ts
@@ -38,7 +38,7 @@ export async function getRumServices({
const { apmEventClient } = setup;
- const response = await apmEventClient.search(params);
+ const response = await apmEventClient.search('get_rum_services', params);
const result = response.aggregations?.services.buckets ?? [];
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts b/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts
index 7cf9066fb4d4d..ae65cdbd121ea 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts
@@ -56,7 +56,7 @@ export async function getUrlSearch({
const { apmEventClient } = setup;
- const response = await apmEventClient.search(params);
+ const response = await apmEventClient.search('get_url_search', params);
const { urls, totalUrls } = response.aggregations ?? {};
const pkey = percentile.toFixed(1);
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts b/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts
index 247b808896e41..9c7a64d7c6481 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_visitor_breakdown.ts
@@ -51,7 +51,7 @@ export async function getVisitorBreakdown({
const { apmEventClient } = setup;
- const response = await apmEventClient.search(params);
+ const response = await apmEventClient.search('get_visitor_breakdown', params);
const { browsers, os } = response.aggregations!;
const totalItems = response.hits.total.value;
diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts b/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts
index 9bde701df5672..bbb301e22aa8d 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts
@@ -103,7 +103,7 @@ export async function getWebCoreVitals({
const { apmEventClient } = setup;
- const response = await apmEventClient.search(params);
+ const response = await apmEventClient.search('get_web_core_vitals', params);
const {
lcp,
cls,
diff --git a/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts b/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts
index 87136fc0538a6..fc5da4ec1d0fa 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts
@@ -51,7 +51,7 @@ export async function hasRumData({
const { apmEventClient } = setup;
- const response = await apmEventClient.search(params);
+ const response = await apmEventClient.search('has_rum_data', params);
return {
indices: setup.indices['apm_oss.transactionIndices']!,
hasData: response.hits.total.value > 0,
diff --git a/x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/index.ts b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/index.ts
index 8fdeb77171862..e0e9bb2ca002f 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/index.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/local_ui_filters/index.ts
@@ -38,37 +38,38 @@ export function getLocalUIFilters({
delete projectionWithoutAggs.body.aggs;
return Promise.all(
- localFilterNames.map(async (name) =>
- withApmSpan('get_ui_filter_options_for_field', async () => {
- const query = getLocalFilterQuery({
- uiFilters,
- projection,
- localUIFilterName: name,
- });
+ localFilterNames.map(async (name) => {
+ const query = getLocalFilterQuery({
+ uiFilters,
+ projection,
+ localUIFilterName: name,
+ });
- const response = await apmEventClient.search(query);
+ const response = await apmEventClient.search(
+ 'get_ui_filter_options_for_field',
+ query
+ );
- const filter = localUIFilters[name];
+ const filter = localUIFilters[name];
- const buckets = response?.aggregations?.by_terms?.buckets ?? [];
+ const buckets = response?.aggregations?.by_terms?.buckets ?? [];
- return {
- ...filter,
- options: orderBy(
- buckets.map((bucket) => {
- return {
- name: bucket.key as string,
- count: bucket.bucket_count
- ? bucket.bucket_count.value
- : bucket.doc_count,
- };
- }),
- 'count',
- 'desc'
- ),
- };
- })
- )
+ return {
+ ...filter,
+ options: orderBy(
+ buckets.map((bucket) => {
+ return {
+ name: bucket.key as string,
+ count: bucket.bucket_count
+ ? bucket.bucket_count.value
+ : bucket.doc_count,
+ };
+ }),
+ 'count',
+ 'desc'
+ ),
+ };
+ })
);
});
}
diff --git a/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts b/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts
index 6047b97651e6a..6ecfe425dc8c5 100644
--- a/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts
@@ -14,44 +14,42 @@ import {
ServiceConnectionNode,
} from '../../../common/service_map';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
-import { withApmSpan } from '../../utils/with_apm_span';
export async function fetchServicePathsFromTraceIds(
setup: Setup & SetupTimeRange,
traceIds: string[]
) {
- return withApmSpan('get_service_paths_from_trace_ids', async () => {
- const { apmEventClient } = setup;
-
- // make sure there's a range so ES can skip shards
- const dayInMs = 24 * 60 * 60 * 1000;
- const start = setup.start - dayInMs;
- const end = setup.end + dayInMs;
-
- const serviceMapParams = {
- apm: {
- events: [ProcessorEvent.span, ProcessorEvent.transaction],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- {
- terms: {
- [TRACE_ID]: traceIds,
- },
+ const { apmEventClient } = setup;
+
+ // make sure there's a range so ES can skip shards
+ const dayInMs = 24 * 60 * 60 * 1000;
+ const start = setup.start - dayInMs;
+ const end = setup.end + dayInMs;
+
+ const serviceMapParams = {
+ apm: {
+ events: [ProcessorEvent.span, ProcessorEvent.transaction],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ {
+ terms: {
+ [TRACE_ID]: traceIds,
},
- ...rangeQuery(start, end),
- ],
- },
+ },
+ ...rangeQuery(start, end),
+ ],
},
- aggs: {
- service_map: {
- scripted_metric: {
- init_script: {
- lang: 'painless',
- source: `state.eventsById = new HashMap();
+ },
+ aggs: {
+ service_map: {
+ scripted_metric: {
+ init_script: {
+ lang: 'painless',
+ source: `state.eventsById = new HashMap();
String[] fieldsToCopy = new String[] {
'parent.id',
@@ -65,10 +63,10 @@ export async function fetchServicePathsFromTraceIds(
'agent.name'
};
state.fieldsToCopy = fieldsToCopy;`,
- },
- map_script: {
- lang: 'painless',
- source: `def id;
+ },
+ map_script: {
+ lang: 'painless',
+ source: `def id;
if (!doc['span.id'].empty) {
id = doc['span.id'].value;
} else {
@@ -85,14 +83,14 @@ export async function fetchServicePathsFromTraceIds(
}
state.eventsById[id] = copy`,
- },
- combine_script: {
- lang: 'painless',
- source: `return state.eventsById;`,
- },
- reduce_script: {
- lang: 'painless',
- source: `
+ },
+ combine_script: {
+ lang: 'painless',
+ source: `return state.eventsById;`,
+ },
+ reduce_script: {
+ lang: 'painless',
+ source: `
def getDestination ( def event ) {
def destination = new HashMap();
destination['span.destination.service.resource'] = event['span.destination.service.resource'];
@@ -208,29 +206,29 @@ export async function fetchServicePathsFromTraceIds(
response.discoveredServices = discoveredServices;
return response;`,
- },
},
},
} as const,
},
- };
-
- const serviceMapFromTraceIdsScriptResponse = await apmEventClient.search(
- serviceMapParams
- );
-
- return serviceMapFromTraceIdsScriptResponse as {
- aggregations?: {
- service_map: {
- value: {
- paths: ConnectionNode[][];
- discoveredServices: Array<{
- from: ExternalConnectionNode;
- to: ServiceConnectionNode;
- }>;
- };
+ },
+ };
+
+ const serviceMapFromTraceIdsScriptResponse = await apmEventClient.search(
+ 'get_service_paths_from_trace_ids',
+ serviceMapParams
+ );
+
+ return serviceMapFromTraceIdsScriptResponse as {
+ aggregations?: {
+ service_map: {
+ value: {
+ paths: ConnectionNode[][];
+ discoveredServices: Array<{
+ from: ExternalConnectionNode;
+ to: ServiceConnectionNode;
+ }>;
};
};
};
- });
+ };
}
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts
index e5b0b72b8784a..6d50023d3fd0e 100644
--- a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts
@@ -87,69 +87,70 @@ async function getConnectionData({
}
async function getServicesData(options: IEnvOptions) {
- return withApmSpan('get_service_stats_for_service_map', async () => {
- const { environment, setup, searchAggregatedTransactions } = options;
+ const { environment, setup, searchAggregatedTransactions } = options;
- const projection = getServicesProjection({
- setup,
- searchAggregatedTransactions,
- });
+ const projection = getServicesProjection({
+ setup,
+ searchAggregatedTransactions,
+ });
- let filter = [
- ...projection.body.query.bool.filter,
- ...environmentQuery(environment),
- ];
+ let filter = [
+ ...projection.body.query.bool.filter,
+ ...environmentQuery(environment),
+ ];
- if (options.serviceName) {
- filter = filter.concat({
- term: {
- [SERVICE_NAME]: options.serviceName,
+ if (options.serviceName) {
+ filter = filter.concat({
+ term: {
+ [SERVICE_NAME]: options.serviceName,
+ },
+ });
+ }
+
+ const params = mergeProjection(projection, {
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ ...projection.body.query.bool,
+ filter,
},
- });
- }
-
- const params = mergeProjection(projection, {
- body: {
- size: 0,
- query: {
- bool: {
- ...projection.body.query.bool,
- filter,
+ },
+ aggs: {
+ services: {
+ terms: {
+ field: projection.body.aggs.services.terms.field,
+ size: 500,
},
- },
- aggs: {
- services: {
- terms: {
- field: projection.body.aggs.services.terms.field,
- size: 500,
- },
- aggs: {
- agent_name: {
- terms: {
- field: AGENT_NAME,
- },
+ aggs: {
+ agent_name: {
+ terms: {
+ field: AGENT_NAME,
},
},
},
},
},
- });
+ },
+ });
- const { apmEventClient } = setup;
+ const { apmEventClient } = setup;
- const response = await apmEventClient.search(params);
+ const response = await apmEventClient.search(
+ 'get_service_stats_for_service_map',
+ params
+ );
- return (
- response.aggregations?.services.buckets.map((bucket) => {
- return {
- [SERVICE_NAME]: bucket.key as string,
- [AGENT_NAME]:
- (bucket.agent_name.buckets[0]?.key as string | undefined) || '',
- [SERVICE_ENVIRONMENT]: options.environment || null,
- };
- }) || []
- );
- });
+ return (
+ response.aggregations?.services.buckets.map((bucket) => {
+ return {
+ [SERVICE_NAME]: bucket.key as string,
+ [AGENT_NAME]:
+ (bucket.agent_name.buckets[0]?.key as string | undefined) || '',
+ [SERVICE_ENVIRONMENT]: options.environment || null,
+ };
+ }) || []
+ );
}
export type ConnectionsResponse = PromiseReturnType;
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts
index 9850c36c573dd..2709fb640d8ce 100644
--- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts
@@ -120,7 +120,7 @@ async function getErrorStats({
});
}
-function getTransactionStats({
+async function getTransactionStats({
setup,
filter,
minutes,
@@ -129,68 +129,70 @@ function getTransactionStats({
avgTransactionDuration: number | null;
avgRequestsPerMinute: number | null;
}> {
- return withApmSpan('get_transaction_stats_for_service_map_node', async () => {
- const { apmEventClient } = setup;
+ const { apmEventClient } = setup;
- const params = {
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- ...filter,
- ...getDocumentTypeFilterForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- {
- terms: {
- [TRANSACTION_TYPE]: [
- TRANSACTION_REQUEST,
- TRANSACTION_PAGE_LOAD,
- ],
- },
+ const params = {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ ...filter,
+ ...getDocumentTypeFilterForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ {
+ terms: {
+ [TRANSACTION_TYPE]: [
+ TRANSACTION_REQUEST,
+ TRANSACTION_PAGE_LOAD,
+ ],
},
- ],
- },
- },
- track_total_hits: true,
- aggs: {
- duration: {
- avg: {
- field: getTransactionDurationFieldForAggregatedTransactions(
- searchAggregatedTransactions
- ),
},
+ ],
+ },
+ },
+ track_total_hits: true,
+ aggs: {
+ duration: {
+ avg: {
+ field: getTransactionDurationFieldForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
},
},
},
- };
- const response = await apmEventClient.search(params);
+ },
+ };
+ const response = await apmEventClient.search(
+ 'get_transaction_stats_for_service_map_node',
+ params
+ );
- const totalRequests = response.hits.total.value;
+ const totalRequests = response.hits.total.value;
- return {
- avgTransactionDuration: response.aggregations?.duration.value ?? null,
- avgRequestsPerMinute: totalRequests > 0 ? totalRequests / minutes : null,
- };
- });
+ return {
+ avgTransactionDuration: response.aggregations?.duration.value ?? null,
+ avgRequestsPerMinute: totalRequests > 0 ? totalRequests / minutes : null,
+ };
}
-function getCpuStats({
+async function getCpuStats({
setup,
filter,
}: TaskParameters): Promise<{ avgCpuUsage: number | null }> {
- return withApmSpan('get_avg_cpu_usage_for_service_map_node', async () => {
- const { apmEventClient } = setup;
+ const { apmEventClient } = setup;
- const response = await apmEventClient.search({
+ const response = await apmEventClient.search(
+ 'get_avg_cpu_usage_for_service_map_node',
+ {
apm: {
events: [ProcessorEvent.metric],
},
@@ -206,10 +208,10 @@ function getCpuStats({
},
aggs: { avgCpuUsage: { avg: { field: METRIC_SYSTEM_CPU_PERCENT } } },
},
- });
+ }
+ );
- return { avgCpuUsage: response.aggregations?.avgCpuUsage.value ?? null };
- });
+ return { avgCpuUsage: response.aggregations?.avgCpuUsage.value ?? null };
}
function getMemoryStats({
@@ -219,7 +221,7 @@ function getMemoryStats({
return withApmSpan('get_memory_stats_for_service_map_node', async () => {
const { apmEventClient } = setup;
- const getAvgMemoryUsage = ({
+ const getAvgMemoryUsage = async ({
additionalFilters,
script,
}: {
@@ -228,8 +230,9 @@ function getMemoryStats({
| typeof percentCgroupMemoryUsedScript
| typeof percentSystemMemoryUsedScript;
}) => {
- return withApmSpan('get_avg_memory_for_service_map_node', async () => {
- const response = await apmEventClient.search({
+ const response = await apmEventClient.search(
+ 'get_avg_memory_for_service_map_node',
+ {
apm: {
events: [ProcessorEvent.metric],
},
@@ -244,9 +247,9 @@ function getMemoryStats({
avgMemoryUsage: { avg: { script } },
},
},
- });
- return response.aggregations?.avgMemoryUsage.value ?? null;
- });
+ }
+ );
+ return response.aggregations?.avgMemoryUsage.value ?? null;
};
let avgMemoryUsage = await getAvgMemoryUsage({
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts
index fa04b963388b2..7894a95cf4d7e 100644
--- a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts
@@ -18,12 +18,11 @@ import {
import { ProcessorEvent } from '../../../common/processor_event';
import { SERVICE_MAP_TIMEOUT_ERROR } from '../../../common/service_map';
import { environmentQuery, rangeQuery } from '../../../server/utils/queries';
-import { withApmSpan } from '../../utils/with_apm_span';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
const MAX_TRACES_TO_INSPECT = 1000;
-export function getTraceSampleIds({
+export async function getTraceSampleIds({
serviceName,
environment,
setup,
@@ -32,90 +31,88 @@ export function getTraceSampleIds({
environment?: string;
setup: Setup & SetupTimeRange;
}) {
- return withApmSpan('get_trace_sample_ids', async () => {
- const { start, end, apmEventClient, config } = setup;
+ const { start, end, apmEventClient, config } = setup;
- const query = {
- bool: {
- filter: [
- {
- exists: {
- field: SPAN_DESTINATION_SERVICE_RESOURCE,
- },
+ const query = {
+ bool: {
+ filter: [
+ {
+ exists: {
+ field: SPAN_DESTINATION_SERVICE_RESOURCE,
},
- ...rangeQuery(start, end),
- ] as ESFilter[],
- },
- } as { bool: { filter: ESFilter[]; must_not?: ESFilter[] | ESFilter } };
+ },
+ ...rangeQuery(start, end),
+ ] as ESFilter[],
+ },
+ } as { bool: { filter: ESFilter[]; must_not?: ESFilter[] | ESFilter } };
- if (serviceName) {
- query.bool.filter.push({ term: { [SERVICE_NAME]: serviceName } });
- }
+ if (serviceName) {
+ query.bool.filter.push({ term: { [SERVICE_NAME]: serviceName } });
+ }
- query.bool.filter.push(...environmentQuery(environment));
+ query.bool.filter.push(...environmentQuery(environment));
- const fingerprintBucketSize = serviceName
- ? config['xpack.apm.serviceMapFingerprintBucketSize']
- : config['xpack.apm.serviceMapFingerprintGlobalBucketSize'];
+ const fingerprintBucketSize = serviceName
+ ? config['xpack.apm.serviceMapFingerprintBucketSize']
+ : config['xpack.apm.serviceMapFingerprintGlobalBucketSize'];
- const traceIdBucketSize = serviceName
- ? config['xpack.apm.serviceMapTraceIdBucketSize']
- : config['xpack.apm.serviceMapTraceIdGlobalBucketSize'];
+ const traceIdBucketSize = serviceName
+ ? config['xpack.apm.serviceMapTraceIdBucketSize']
+ : config['xpack.apm.serviceMapTraceIdGlobalBucketSize'];
- const samplerShardSize = traceIdBucketSize * 10;
+ const samplerShardSize = traceIdBucketSize * 10;
- const params = {
- apm: {
- events: [ProcessorEvent.span],
- },
- body: {
- size: 0,
- query,
- aggs: {
- connections: {
- composite: {
- sources: asMutableArray([
- {
- [SPAN_DESTINATION_SERVICE_RESOURCE]: {
- terms: {
- field: SPAN_DESTINATION_SERVICE_RESOURCE,
- },
+ const params = {
+ apm: {
+ events: [ProcessorEvent.span],
+ },
+ body: {
+ size: 0,
+ query,
+ aggs: {
+ connections: {
+ composite: {
+ sources: asMutableArray([
+ {
+ [SPAN_DESTINATION_SERVICE_RESOURCE]: {
+ terms: {
+ field: SPAN_DESTINATION_SERVICE_RESOURCE,
},
},
- {
- [SERVICE_NAME]: {
- terms: {
- field: SERVICE_NAME,
- },
+ },
+ {
+ [SERVICE_NAME]: {
+ terms: {
+ field: SERVICE_NAME,
},
},
- {
- [SERVICE_ENVIRONMENT]: {
- terms: {
- field: SERVICE_ENVIRONMENT,
- missing_bucket: true,
- },
+ },
+ {
+ [SERVICE_ENVIRONMENT]: {
+ terms: {
+ field: SERVICE_ENVIRONMENT,
+ missing_bucket: true,
},
},
- ] as const),
- size: fingerprintBucketSize,
- },
- aggs: {
- sample: {
- sampler: {
- shard_size: samplerShardSize,
- },
- aggs: {
- trace_ids: {
- terms: {
- field: TRACE_ID,
- size: traceIdBucketSize,
- execution_hint: 'map' as const,
- // remove bias towards large traces by sorting on trace.id
- // which will be random-esque
- order: {
- _key: 'desc' as const,
- },
+ },
+ ] as const),
+ size: fingerprintBucketSize,
+ },
+ aggs: {
+ sample: {
+ sampler: {
+ shard_size: samplerShardSize,
+ },
+ aggs: {
+ trace_ids: {
+ terms: {
+ field: TRACE_ID,
+ size: traceIdBucketSize,
+ execution_hint: 'map' as const,
+ // remove bias towards large traces by sorting on trace.id
+ // which will be random-esque
+ order: {
+ _key: 'desc' as const,
},
},
},
@@ -124,34 +121,36 @@ export function getTraceSampleIds({
},
},
},
- };
+ },
+ };
- try {
- const tracesSampleResponse = await apmEventClient.search(params);
- // make sure at least one trace per composite/connection bucket
- // is queried
- const traceIdsWithPriority =
- tracesSampleResponse.aggregations?.connections.buckets.flatMap(
- (bucket) =>
- bucket.sample.trace_ids.buckets.map((sampleDocBucket, index) => ({
- traceId: sampleDocBucket.key as string,
- priority: index,
- }))
- ) || [];
+ try {
+ const tracesSampleResponse = await apmEventClient.search(
+ 'get_trace_sample_ids',
+ params
+ );
+ // make sure at least one trace per composite/connection bucket
+ // is queried
+ const traceIdsWithPriority =
+ tracesSampleResponse.aggregations?.connections.buckets.flatMap((bucket) =>
+ bucket.sample.trace_ids.buckets.map((sampleDocBucket, index) => ({
+ traceId: sampleDocBucket.key as string,
+ priority: index,
+ }))
+ ) || [];
- const traceIds = take(
- uniq(
- sortBy(traceIdsWithPriority, 'priority').map(({ traceId }) => traceId)
- ),
- MAX_TRACES_TO_INSPECT
- );
+ const traceIds = take(
+ uniq(
+ sortBy(traceIdsWithPriority, 'priority').map(({ traceId }) => traceId)
+ ),
+ MAX_TRACES_TO_INSPECT
+ );
- return { traceIds };
- } catch (error) {
- if ('displayName' in error && error.displayName === 'RequestTimeout') {
- throw Boom.internal(SERVICE_MAP_TIMEOUT_ERROR);
- }
- throw error;
+ return { traceIds };
+ } catch (error) {
+ if ('displayName' in error && error.displayName === 'RequestTimeout') {
+ throw Boom.internal(SERVICE_MAP_TIMEOUT_ERROR);
}
- });
+ throw error;
+ }
}
diff --git a/x-pack/plugins/apm/server/lib/service_nodes/index.ts b/x-pack/plugins/apm/server/lib/service_nodes/index.ts
index 07b7e532d8055..97c553f344205 100644
--- a/x-pack/plugins/apm/server/lib/service_nodes/index.ts
+++ b/x-pack/plugins/apm/server/lib/service_nodes/index.ts
@@ -14,10 +14,9 @@ import {
import { SERVICE_NODE_NAME_MISSING } from '../../../common/service_nodes';
import { getServiceNodesProjection } from '../../projections/service_nodes';
import { mergeProjection } from '../../projections/util/merge_projection';
-import { withApmSpan } from '../../utils/with_apm_span';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
-const getServiceNodes = ({
+const getServiceNodes = async ({
kuery,
setup,
serviceName,
@@ -26,69 +25,67 @@ const getServiceNodes = ({
setup: Setup & SetupTimeRange;
serviceName: string;
}) => {
- return withApmSpan('get_service_nodes', async () => {
- const { apmEventClient } = setup;
+ const { apmEventClient } = setup;
- const projection = getServiceNodesProjection({ kuery, setup, serviceName });
+ const projection = getServiceNodesProjection({ kuery, setup, serviceName });
- const params = mergeProjection(projection, {
- body: {
- aggs: {
- nodes: {
- terms: {
- ...projection.body.aggs.nodes.terms,
- size: 10000,
- missing: SERVICE_NODE_NAME_MISSING,
- },
- aggs: {
- cpu: {
- avg: {
- field: METRIC_PROCESS_CPU_PERCENT,
- },
+ const params = mergeProjection(projection, {
+ body: {
+ aggs: {
+ nodes: {
+ terms: {
+ ...projection.body.aggs.nodes.terms,
+ size: 10000,
+ missing: SERVICE_NODE_NAME_MISSING,
+ },
+ aggs: {
+ cpu: {
+ avg: {
+ field: METRIC_PROCESS_CPU_PERCENT,
},
- heapMemory: {
- avg: {
- field: METRIC_JAVA_HEAP_MEMORY_USED,
- },
+ },
+ heapMemory: {
+ avg: {
+ field: METRIC_JAVA_HEAP_MEMORY_USED,
},
- nonHeapMemory: {
- avg: {
- field: METRIC_JAVA_NON_HEAP_MEMORY_USED,
- },
+ },
+ nonHeapMemory: {
+ avg: {
+ field: METRIC_JAVA_NON_HEAP_MEMORY_USED,
},
- threadCount: {
- max: {
- field: METRIC_JAVA_THREAD_COUNT,
- },
+ },
+ threadCount: {
+ max: {
+ field: METRIC_JAVA_THREAD_COUNT,
},
},
},
},
},
- });
+ },
+ });
- const response = await apmEventClient.search(params);
+ const response = await apmEventClient.search('get_service_nodes', params);
- if (!response.aggregations) {
- return [];
- }
+ if (!response.aggregations) {
+ return [];
+ }
- return response.aggregations.nodes.buckets
- .map((bucket) => ({
- name: bucket.key as string,
- cpu: bucket.cpu.value,
- heapMemory: bucket.heapMemory.value,
- nonHeapMemory: bucket.nonHeapMemory.value,
- threadCount: bucket.threadCount.value,
- }))
- .filter(
- (item) =>
- item.cpu !== null ||
- item.heapMemory !== null ||
- item.nonHeapMemory !== null ||
- item.threadCount != null
- );
- });
+ return response.aggregations.nodes.buckets
+ .map((bucket) => ({
+ name: bucket.key as string,
+ cpu: bucket.cpu.value,
+ heapMemory: bucket.heapMemory.value,
+ nonHeapMemory: bucket.nonHeapMemory.value,
+ threadCount: bucket.threadCount.value,
+ }))
+ .filter(
+ (item) =>
+ item.cpu !== null ||
+ item.heapMemory !== null ||
+ item.nonHeapMemory !== null ||
+ item.threadCount != null
+ );
};
export { getServiceNodes };
diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap
index 9d05369aca840..2f653e2c4df1d 100644
--- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap
@@ -22,6 +22,7 @@ Object {
"events": Array [
"transaction",
],
+ "includeLegacyData": true,
},
"body": Object {
"query": Object {
diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts
index 611f9b18a0b1a..202b5075d2ea7 100644
--- a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts
+++ b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts
@@ -13,7 +13,6 @@ import {
SERVICE_VERSION,
} from '../../../../common/elasticsearch_fieldnames';
import { environmentQuery, rangeQuery } from '../../../../server/utils/queries';
-import { withApmSpan } from '../../../utils/with_apm_span';
import {
getDocumentTypeFilterForAggregatedTransactions,
getProcessorEventForAggregatedTransactions,
@@ -31,20 +30,52 @@ export async function getDerivedServiceAnnotations({
setup: Setup & SetupTimeRange;
searchAggregatedTransactions: boolean;
}) {
- return withApmSpan('get_derived_service_annotations', async () => {
- const { start, end, apmEventClient } = setup;
+ const { start, end, apmEventClient } = setup;
- const filter: ESFilter[] = [
- { term: { [SERVICE_NAME]: serviceName } },
- ...getDocumentTypeFilterForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ...environmentQuery(environment),
- ];
+ const filter: ESFilter[] = [
+ { term: { [SERVICE_NAME]: serviceName } },
+ ...getDocumentTypeFilterForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ...environmentQuery(environment),
+ ];
- const versions =
- (
- await apmEventClient.search({
+ const versions =
+ (
+ await apmEventClient.search('get_derived_service_annotations', {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [...filter, ...rangeQuery(start, end)],
+ },
+ },
+ aggs: {
+ versions: {
+ terms: {
+ field: SERVICE_VERSION,
+ },
+ },
+ },
+ },
+ })
+ ).aggregations?.versions.buckets.map((bucket) => bucket.key) ?? [];
+
+ if (versions.length <= 1) {
+ return [];
+ }
+ const annotations = await Promise.all(
+ versions.map(async (version) => {
+ const response = await apmEventClient.search(
+ 'get_first_seen_of_version',
+ {
apm: {
events: [
getProcessorEventForAggregatedTransactions(
@@ -53,73 +84,40 @@ export async function getDerivedServiceAnnotations({
],
},
body: {
- size: 0,
+ size: 1,
query: {
bool: {
- filter: [...filter, ...rangeQuery(start, end)],
+ filter: [...filter, { term: { [SERVICE_VERSION]: version } }],
},
},
- aggs: {
- versions: {
- terms: {
- field: SERVICE_VERSION,
- },
- },
+ sort: {
+ '@timestamp': 'asc',
},
},
- })
- ).aggregations?.versions.buckets.map((bucket) => bucket.key) ?? [];
+ }
+ );
- if (versions.length <= 1) {
- return [];
- }
- const annotations = await Promise.all(
- versions.map(async (version) => {
- return withApmSpan('get_first_seen_of_version', async () => {
- const response = await apmEventClient.search({
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ],
- },
- body: {
- size: 1,
- query: {
- bool: {
- filter: [...filter, { term: { [SERVICE_VERSION]: version } }],
- },
- },
- sort: {
- '@timestamp': 'asc',
- },
- },
- });
-
- const firstSeen = new Date(
- response.hits.hits[0]._source['@timestamp']
- ).getTime();
+ const firstSeen = new Date(
+ response.hits.hits[0]._source['@timestamp']
+ ).getTime();
- if (!isFiniteNumber(firstSeen)) {
- throw new Error(
- 'First seen for version was unexpectedly undefined or null.'
- );
- }
+ if (!isFiniteNumber(firstSeen)) {
+ throw new Error(
+ 'First seen for version was unexpectedly undefined or null.'
+ );
+ }
- if (firstSeen < start || firstSeen > end) {
- return null;
- }
+ if (firstSeen < start || firstSeen > end) {
+ return null;
+ }
- return {
- type: AnnotationType.VERSION,
- id: version,
- '@timestamp': firstSeen,
- text: version,
- };
- });
- })
- );
- return annotations.filter(Boolean) as Annotation[];
- });
+ return {
+ type: AnnotationType.VERSION,
+ id: version,
+ '@timestamp': firstSeen,
+ text: version,
+ };
+ })
+ );
+ return annotations.filter(Boolean) as Annotation[];
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts b/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts
index a81c0b2fc2c44..82147d7c94236 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_agent_name.ts
@@ -13,9 +13,8 @@ import {
import { rangeQuery } from '../../../server/utils/queries';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions';
-import { withApmSpan } from '../../utils/with_apm_span';
-export function getServiceAgentName({
+export async function getServiceAgentName({
serviceName,
setup,
searchAggregatedTransactions,
@@ -24,42 +23,41 @@ export function getServiceAgentName({
setup: Setup & SetupTimeRange;
searchAggregatedTransactions: boolean;
}) {
- return withApmSpan('get_service_agent_name', async () => {
- const { start, end, apmEventClient } = setup;
+ const { start, end, apmEventClient } = setup;
- const params = {
- terminateAfter: 1,
- apm: {
- events: [
- ProcessorEvent.error,
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ProcessorEvent.metric,
- ],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- { term: { [SERVICE_NAME]: serviceName } },
- ...rangeQuery(start, end),
- ],
- },
+ const params = {
+ terminateAfter: 1,
+ apm: {
+ events: [
+ ProcessorEvent.error,
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ProcessorEvent.metric,
+ ],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ { term: { [SERVICE_NAME]: serviceName } },
+ ...rangeQuery(start, end),
+ ],
},
- aggs: {
- agents: {
- terms: { field: AGENT_NAME, size: 1 },
- },
+ },
+ aggs: {
+ agents: {
+ terms: { field: AGENT_NAME, size: 1 },
},
},
- };
+ },
+ };
- const { aggregations } = await apmEventClient.search(params);
- const agentName = aggregations?.agents.buckets[0]?.key as
- | string
- | undefined;
- return { agentName };
- });
+ const { aggregations } = await apmEventClient.search(
+ 'get_service_agent_name',
+ params
+ );
+ const agentName = aggregations?.agents.buckets[0]?.key as string | undefined;
+ return { agentName };
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts
index db491012c986b..4993484f5b240 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts
@@ -38,56 +38,54 @@ export const getDestinationMap = ({
return withApmSpan('get_service_destination_map', async () => {
const { start, end, apmEventClient } = setup;
- const response = await withApmSpan('get_exit_span_samples', async () =>
- apmEventClient.search({
- apm: {
- events: [ProcessorEvent.span],
+ const response = await apmEventClient.search('get_exit_span_samples', {
+ apm: {
+ events: [ProcessorEvent.span],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ { term: { [SERVICE_NAME]: serviceName } },
+ { exists: { field: SPAN_DESTINATION_SERVICE_RESOURCE } },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ],
+ },
},
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- { term: { [SERVICE_NAME]: serviceName } },
- { exists: { field: SPAN_DESTINATION_SERVICE_RESOURCE } },
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ],
+ aggs: {
+ connections: {
+ composite: {
+ size: 1000,
+ sources: asMutableArray([
+ {
+ [SPAN_DESTINATION_SERVICE_RESOURCE]: {
+ terms: { field: SPAN_DESTINATION_SERVICE_RESOURCE },
+ },
+ },
+ // make sure we get samples for both successful
+ // and failed calls
+ { [EVENT_OUTCOME]: { terms: { field: EVENT_OUTCOME } } },
+ ] as const),
},
- },
- aggs: {
- connections: {
- composite: {
- size: 1000,
- sources: asMutableArray([
- {
- [SPAN_DESTINATION_SERVICE_RESOURCE]: {
- terms: { field: SPAN_DESTINATION_SERVICE_RESOURCE },
+ aggs: {
+ sample: {
+ top_hits: {
+ size: 1,
+ _source: [SPAN_TYPE, SPAN_SUBTYPE, SPAN_ID],
+ sort: [
+ {
+ '@timestamp': 'desc' as const,
},
- },
- // make sure we get samples for both successful
- // and failed calls
- { [EVENT_OUTCOME]: { terms: { field: EVENT_OUTCOME } } },
- ] as const),
- },
- aggs: {
- sample: {
- top_hits: {
- size: 1,
- _source: [SPAN_TYPE, SPAN_SUBTYPE, SPAN_ID],
- sort: [
- {
- '@timestamp': 'desc' as const,
- },
- ],
- },
+ ],
},
},
},
},
},
- })
- );
+ },
+ });
const outgoingConnections =
response.aggregations?.connections.buckets.map((bucket) => {
@@ -103,38 +101,37 @@ export const getDestinationMap = ({
};
}) ?? [];
- const transactionResponse = await withApmSpan(
+ const transactionResponse = await apmEventClient.search(
'get_transactions_for_exit_spans',
- () =>
- apmEventClient.search({
- apm: {
- events: [ProcessorEvent.transaction],
- },
- body: {
- query: {
- bool: {
- filter: [
- {
- terms: {
- [PARENT_ID]: outgoingConnections.map(
- (connection) => connection[SPAN_ID]
- ),
- },
+ {
+ apm: {
+ events: [ProcessorEvent.transaction],
+ },
+ body: {
+ query: {
+ bool: {
+ filter: [
+ {
+ terms: {
+ [PARENT_ID]: outgoingConnections.map(
+ (connection) => connection[SPAN_ID]
+ ),
},
- ...rangeQuery(start, end),
- ],
- },
+ },
+ ...rangeQuery(start, end),
+ ],
},
- size: outgoingConnections.length,
- docvalue_fields: asMutableArray([
- SERVICE_NAME,
- SERVICE_ENVIRONMENT,
- AGENT_NAME,
- PARENT_ID,
- ] as const),
- _source: false,
},
- })
+ size: outgoingConnections.length,
+ docvalue_fields: asMutableArray([
+ SERVICE_NAME,
+ SERVICE_ENVIRONMENT,
+ AGENT_NAME,
+ PARENT_ID,
+ ] as const),
+ _source: false,
+ },
+ }
);
const incomingConnections = transactionResponse.hits.hits.map((hit) => ({
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts
index c8642c6272b5f..1d815dd7180e3 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts
@@ -18,9 +18,8 @@ import { environmentQuery, rangeQuery } from '../../../../server/utils/queries';
import { getBucketSize } from '../../helpers/get_bucket_size';
import { EventOutcome } from '../../../../common/event_outcome';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
-import { withApmSpan } from '../../../utils/with_apm_span';
-export const getMetrics = ({
+export const getMetrics = async ({
setup,
serviceName,
environment,
@@ -31,10 +30,11 @@ export const getMetrics = ({
environment?: string;
numBuckets: number;
}) => {
- return withApmSpan('get_service_destination_metrics', async () => {
- const { start, end, apmEventClient } = setup;
+ const { start, end, apmEventClient } = setup;
- const response = await apmEventClient.search({
+ const response = await apmEventClient.search(
+ 'get_service_destination_metrics',
+ {
apm: {
events: [ProcessorEvent.metric],
},
@@ -46,7 +46,9 @@ export const getMetrics = ({
filter: [
{ term: { [SERVICE_NAME]: serviceName } },
{
- exists: { field: SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT },
+ exists: {
+ field: SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT,
+ },
},
...rangeQuery(start, end),
...environmentQuery(environment),
@@ -99,47 +101,47 @@ export const getMetrics = ({
},
},
},
- });
+ }
+ );
- return (
- response.aggregations?.connections.buckets.map((bucket) => ({
- span: {
- destination: {
- service: {
- resource: String(bucket.key),
- },
+ return (
+ response.aggregations?.connections.buckets.map((bucket) => ({
+ span: {
+ destination: {
+ service: {
+ resource: String(bucket.key),
},
},
- value: {
- count: sum(
- bucket.timeseries.buckets.map(
- (dateBucket) => dateBucket.count.value ?? 0
- )
- ),
- latency_sum: sum(
- bucket.timeseries.buckets.map(
- (dateBucket) => dateBucket.latency_sum.value ?? 0
- )
- ),
- error_count: sum(
- bucket.timeseries.buckets.flatMap(
- (dateBucket) =>
- dateBucket[EVENT_OUTCOME].buckets.find(
- (outcomeBucket) => outcomeBucket.key === EventOutcome.failure
- )?.count.value ?? 0
- )
- ),
- },
- timeseries: bucket.timeseries.buckets.map((dateBucket) => ({
- x: dateBucket.key,
- count: dateBucket.count.value ?? 0,
- latency_sum: dateBucket.latency_sum.value ?? 0,
- error_count:
- dateBucket[EVENT_OUTCOME].buckets.find(
- (outcomeBucket) => outcomeBucket.key === EventOutcome.failure
- )?.count.value ?? 0,
- })),
- })) ?? []
- );
- });
+ },
+ value: {
+ count: sum(
+ bucket.timeseries.buckets.map(
+ (dateBucket) => dateBucket.count.value ?? 0
+ )
+ ),
+ latency_sum: sum(
+ bucket.timeseries.buckets.map(
+ (dateBucket) => dateBucket.latency_sum.value ?? 0
+ )
+ ),
+ error_count: sum(
+ bucket.timeseries.buckets.flatMap(
+ (dateBucket) =>
+ dateBucket[EVENT_OUTCOME].buckets.find(
+ (outcomeBucket) => outcomeBucket.key === EventOutcome.failure
+ )?.count.value ?? 0
+ )
+ ),
+ },
+ timeseries: bucket.timeseries.buckets.map((dateBucket) => ({
+ x: dateBucket.key,
+ count: dateBucket.count.value ?? 0,
+ latency_sum: dateBucket.latency_sum.value ?? 0,
+ error_count:
+ dateBucket[EVENT_OUTCOME].buckets.find(
+ (outcomeBucket) => outcomeBucket.key === EventOutcome.failure
+ )?.count.value ?? 0,
+ })),
+ })) ?? []
+ );
};
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts
index e45864de2fc1e..bd69bfc53db71 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts
@@ -18,7 +18,6 @@ import {
rangeQuery,
kqlQuery,
} from '../../../../server/utils/queries';
-import { withApmSpan } from '../../../utils/with_apm_span';
import { getBucketSize } from '../../helpers/get_bucket_size';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
@@ -43,75 +42,71 @@ export async function getServiceErrorGroupDetailedStatistics({
start: number;
end: number;
}): Promise> {
- return withApmSpan(
- 'get_service_error_group_detailed_statistics',
- async () => {
- const { apmEventClient } = setup;
+ const { apmEventClient } = setup;
- const { intervalString } = getBucketSize({ start, end, numBuckets });
+ const { intervalString } = getBucketSize({ start, end, numBuckets });
- const timeseriesResponse = await apmEventClient.search({
- apm: {
- events: [ProcessorEvent.error],
+ const timeseriesResponse = await apmEventClient.search(
+ 'get_service_error_group_detailed_statistics',
+ {
+ apm: {
+ events: [ProcessorEvent.error],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ { terms: { [ERROR_GROUP_ID]: groupIds } },
+ { term: { [SERVICE_NAME]: serviceName } },
+ { term: { [TRANSACTION_TYPE]: transactionType } },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ],
+ },
},
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- { terms: { [ERROR_GROUP_ID]: groupIds } },
- { term: { [SERVICE_NAME]: serviceName } },
- { term: { [TRANSACTION_TYPE]: transactionType } },
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ],
+ aggs: {
+ error_groups: {
+ terms: {
+ field: ERROR_GROUP_ID,
+ size: 500,
},
- },
- aggs: {
- error_groups: {
- terms: {
- field: ERROR_GROUP_ID,
- size: 500,
- },
- aggs: {
- timeseries: {
- date_histogram: {
- field: '@timestamp',
- fixed_interval: intervalString,
- min_doc_count: 0,
- extended_bounds: {
- min: start,
- max: end,
- },
+ aggs: {
+ timeseries: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: intervalString,
+ min_doc_count: 0,
+ extended_bounds: {
+ min: start,
+ max: end,
},
},
},
},
},
},
- });
-
- if (!timeseriesResponse.aggregations) {
- return [];
- }
-
- return timeseriesResponse.aggregations.error_groups.buckets.map(
- (bucket) => {
- const groupId = bucket.key as string;
- return {
- groupId,
- timeseries: bucket.timeseries.buckets.map((timeseriesBucket) => {
- return {
- x: timeseriesBucket.key,
- y: timeseriesBucket.doc_count,
- };
- }),
- };
- }
- );
+ },
}
);
+
+ if (!timeseriesResponse.aggregations) {
+ return [];
+ }
+
+ return timeseriesResponse.aggregations.error_groups.buckets.map((bucket) => {
+ const groupId = bucket.key as string;
+ return {
+ groupId,
+ timeseries: bucket.timeseries.buckets.map((timeseriesBucket) => {
+ return {
+ x: timeseriesBucket.key,
+ y: timeseriesBucket.doc_count,
+ };
+ }),
+ };
+ });
}
export async function getServiceErrorGroupPeriods({
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_main_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_main_statistics.ts
index 361c92244aee0..8168c0d5549aa 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_main_statistics.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_main_statistics.ts
@@ -19,11 +19,10 @@ import {
rangeQuery,
kqlQuery,
} from '../../../../server/utils/queries';
-import { withApmSpan } from '../../../utils/with_apm_span';
import { getErrorName } from '../../helpers/get_error_name';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
-export function getServiceErrorGroupMainStatistics({
+export async function getServiceErrorGroupMainStatistics({
kuery,
serviceName,
setup,
@@ -36,10 +35,11 @@ export function getServiceErrorGroupMainStatistics({
transactionType: string;
environment?: string;
}) {
- return withApmSpan('get_service_error_group_main_statistics', async () => {
- const { apmEventClient, start, end } = setup;
+ const { apmEventClient, start, end } = setup;
- const response = await apmEventClient.search({
+ const response = await apmEventClient.search(
+ 'get_service_error_group_main_statistics',
+ {
apm: {
events: [ProcessorEvent.error],
},
@@ -79,24 +79,23 @@ export function getServiceErrorGroupMainStatistics({
},
},
},
- });
+ }
+ );
- const errorGroups =
- response.aggregations?.error_groups.buckets.map((bucket) => ({
- group_id: bucket.key as string,
- name:
- getErrorName(bucket.sample.hits.hits[0]._source) ??
- NOT_AVAILABLE_LABEL,
- last_seen: new Date(
- bucket.sample.hits.hits[0]?._source['@timestamp']
- ).getTime(),
- occurrences: bucket.doc_count,
- })) ?? [];
+ const errorGroups =
+ response.aggregations?.error_groups.buckets.map((bucket) => ({
+ group_id: bucket.key as string,
+ name:
+ getErrorName(bucket.sample.hits.hits[0]._source) ?? NOT_AVAILABLE_LABEL,
+ last_seen: new Date(
+ bucket.sample.hits.hits[0]?._source['@timestamp']
+ ).getTime(),
+ occurrences: bucket.doc_count,
+ })) ?? [];
- return {
- is_aggregation_accurate:
- (response.aggregations?.error_groups.sum_other_doc_count ?? 0) === 0,
- error_groups: errorGroups,
- };
- });
+ return {
+ is_aggregation_accurate:
+ (response.aggregations?.error_groups.sum_other_doc_count ?? 0) === 0,
+ error_groups: errorGroups,
+ };
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts
index 7729822df30ca..b720c56464c30 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts
@@ -59,8 +59,9 @@ export async function getServiceErrorGroups({
const { intervalString } = getBucketSize({ start, end, numBuckets });
- const response = await withApmSpan('get_top_service_error_groups', () =>
- apmEventClient.search({
+ const response = await apmEventClient.search(
+ 'get_top_service_error_groups',
+ {
apm: {
events: [ProcessorEvent.error],
},
@@ -104,7 +105,7 @@ export async function getServiceErrorGroups({
},
},
},
- })
+ }
);
const errorGroups =
@@ -139,50 +140,49 @@ export async function getServiceErrorGroups({
(group) => group.group_id
);
- const timeseriesResponse = await withApmSpan(
+ const timeseriesResponse = await apmEventClient.search(
'get_service_error_groups_timeseries',
- async () =>
- apmEventClient.search({
- apm: {
- events: [ProcessorEvent.error],
+ {
+ apm: {
+ events: [ProcessorEvent.error],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ { terms: { [ERROR_GROUP_ID]: sortedErrorGroupIds } },
+ { term: { [SERVICE_NAME]: serviceName } },
+ { term: { [TRANSACTION_TYPE]: transactionType } },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ],
+ },
},
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- { terms: { [ERROR_GROUP_ID]: sortedErrorGroupIds } },
- { term: { [SERVICE_NAME]: serviceName } },
- { term: { [TRANSACTION_TYPE]: transactionType } },
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ],
+ aggs: {
+ error_groups: {
+ terms: {
+ field: ERROR_GROUP_ID,
+ size,
},
- },
- aggs: {
- error_groups: {
- terms: {
- field: ERROR_GROUP_ID,
- size,
- },
- aggs: {
- timeseries: {
- date_histogram: {
- field: '@timestamp',
- fixed_interval: intervalString,
- min_doc_count: 0,
- extended_bounds: {
- min: start,
- max: end,
- },
+ aggs: {
+ timeseries: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: intervalString,
+ min_doc_count: 0,
+ extended_bounds: {
+ min: start,
+ max: end,
},
},
},
},
},
},
- })
+ },
+ }
);
return {
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instance_metadata_details.ts b/x-pack/plugins/apm/server/lib/services/get_service_instance_metadata_details.ts
index 25935bcc37dff..bdf9530a9c0c7 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_instance_metadata_details.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_instance_metadata_details.ts
@@ -11,7 +11,6 @@ import {
TRANSACTION_TYPE,
} from '../../../common/elasticsearch_fieldnames';
import { environmentQuery, kqlQuery, rangeQuery } from '../../utils/queries';
-import { withApmSpan } from '../../utils/with_apm_span';
import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
@@ -37,18 +36,19 @@ export async function getServiceInstanceMetadataDetails({
environment?: string;
kuery?: string;
}) {
- return withApmSpan('get_service_instance_metadata_details', async () => {
- const { start, end, apmEventClient } = setup;
- const filter = [
- { term: { [SERVICE_NAME]: serviceName } },
- { term: { [SERVICE_NODE_NAME]: serviceNodeName } },
- { term: { [TRANSACTION_TYPE]: transactionType } },
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ];
+ const { start, end, apmEventClient } = setup;
+ const filter = [
+ { term: { [SERVICE_NAME]: serviceName } },
+ { term: { [SERVICE_NODE_NAME]: serviceNodeName } },
+ { term: { [TRANSACTION_TYPE]: transactionType } },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ];
- const response = await apmEventClient.search({
+ const response = await apmEventClient.search(
+ 'get_service_instance_metadata_details',
+ {
apm: {
events: [
getProcessorEventForAggregatedTransactions(
@@ -61,24 +61,24 @@ export async function getServiceInstanceMetadataDetails({
size: 1,
query: { bool: { filter } },
},
- });
+ }
+ );
- const sample = response.hits.hits[0]?._source;
+ const sample = response.hits.hits[0]?._source;
- if (!sample) {
- return {};
- }
+ if (!sample) {
+ return {};
+ }
- const { agent, service, container, kubernetes, host, cloud } = sample;
+ const { agent, service, container, kubernetes, host, cloud } = sample;
- return {
- '@timestamp': sample['@timestamp'],
- agent,
- service,
- container,
- kubernetes,
- host,
- cloud,
- };
- });
+ return {
+ '@timestamp': sample['@timestamp'],
+ agent,
+ service,
+ container,
+ kubernetes,
+ host,
+ cloud,
+ };
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts
index 1a33e9810dd5e..526ae19143f13 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts
@@ -24,7 +24,6 @@ import {
percentCgroupMemoryUsedScript,
percentSystemMemoryUsedScript,
} from '../../metrics/by_agent/shared/memory';
-import { withApmSpan } from '../../../utils/with_apm_span';
interface ServiceInstanceSystemMetricPrimaryStatistics {
serviceNodeName: string;
@@ -67,142 +66,140 @@ export async function getServiceInstancesSystemMetricStatistics<
size?: number;
isComparisonSearch: T;
}): Promise>> {
- return withApmSpan(
- 'get_service_instances_system_metric_statistics',
- async () => {
- const { apmEventClient } = setup;
-
- const { intervalString } = getBucketSize({ start, end, numBuckets });
-
- const systemMemoryFilter = {
- bool: {
- filter: [
- { exists: { field: METRIC_SYSTEM_FREE_MEMORY } },
- { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
- ],
- },
- };
-
- const cgroupMemoryFilter = {
- exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES },
- };
-
- const cpuUsageFilter = { exists: { field: METRIC_PROCESS_CPU_PERCENT } };
-
- function withTimeseries(
- agg: TParams
- ) {
- return {
- ...(isComparisonSearch
- ? {
- avg: { avg: agg },
- timeseries: {
- date_histogram: {
- field: '@timestamp',
- fixed_interval: intervalString,
- min_doc_count: 0,
- extended_bounds: {
- min: start,
- max: end,
- },
- },
- aggs: { avg: { avg: agg } },
+ const { apmEventClient } = setup;
+
+ const { intervalString } = getBucketSize({ start, end, numBuckets });
+
+ const systemMemoryFilter = {
+ bool: {
+ filter: [
+ { exists: { field: METRIC_SYSTEM_FREE_MEMORY } },
+ { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
+ ],
+ },
+ };
+
+ const cgroupMemoryFilter = {
+ exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES },
+ };
+
+ const cpuUsageFilter = { exists: { field: METRIC_PROCESS_CPU_PERCENT } };
+
+ function withTimeseries(
+ agg: TParams
+ ) {
+ return {
+ ...(isComparisonSearch
+ ? {
+ avg: { avg: agg },
+ timeseries: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: intervalString,
+ min_doc_count: 0,
+ extended_bounds: {
+ min: start,
+ max: end,
},
- }
- : { avg: { avg: agg } }),
- };
- }
-
- const subAggs = {
- memory_usage_cgroup: {
- filter: cgroupMemoryFilter,
- aggs: withTimeseries({ script: percentCgroupMemoryUsedScript }),
- },
- memory_usage_system: {
- filter: systemMemoryFilter,
- aggs: withTimeseries({ script: percentSystemMemoryUsedScript }),
- },
- cpu_usage: {
- filter: cpuUsageFilter,
- aggs: withTimeseries({ field: METRIC_PROCESS_CPU_PERCENT }),
- },
- };
-
- const response = await apmEventClient.search({
- apm: {
- events: [ProcessorEvent.metric],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- { term: { [SERVICE_NAME]: serviceName } },
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ...(isComparisonSearch && serviceNodeIds
- ? [{ terms: { [SERVICE_NODE_NAME]: serviceNodeIds } }]
- : []),
- ],
- should: [cgroupMemoryFilter, systemMemoryFilter, cpuUsageFilter],
- minimum_should_match: 1,
+ },
+ aggs: { avg: { avg: agg } },
},
+ }
+ : { avg: { avg: agg } }),
+ };
+ }
+
+ const subAggs = {
+ memory_usage_cgroup: {
+ filter: cgroupMemoryFilter,
+ aggs: withTimeseries({ script: percentCgroupMemoryUsedScript }),
+ },
+ memory_usage_system: {
+ filter: systemMemoryFilter,
+ aggs: withTimeseries({ script: percentSystemMemoryUsedScript }),
+ },
+ cpu_usage: {
+ filter: cpuUsageFilter,
+ aggs: withTimeseries({ field: METRIC_PROCESS_CPU_PERCENT }),
+ },
+ };
+
+ const response = await apmEventClient.search(
+ 'get_service_instances_system_metric_statistics',
+ {
+ apm: {
+ events: [ProcessorEvent.metric],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ { term: { [SERVICE_NAME]: serviceName } },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ...(isComparisonSearch && serviceNodeIds
+ ? [{ terms: { [SERVICE_NODE_NAME]: serviceNodeIds } }]
+ : []),
+ ],
+ should: [cgroupMemoryFilter, systemMemoryFilter, cpuUsageFilter],
+ minimum_should_match: 1,
},
- aggs: {
- [SERVICE_NODE_NAME]: {
- terms: {
- field: SERVICE_NODE_NAME,
- missing: SERVICE_NODE_NAME_MISSING,
- ...(size ? { size } : {}),
- ...(isComparisonSearch ? { include: serviceNodeIds } : {}),
- },
- aggs: subAggs,
+ },
+ aggs: {
+ [SERVICE_NODE_NAME]: {
+ terms: {
+ field: SERVICE_NODE_NAME,
+ missing: SERVICE_NODE_NAME_MISSING,
+ ...(size ? { size } : {}),
+ ...(isComparisonSearch ? { include: serviceNodeIds } : {}),
},
+ aggs: subAggs,
},
},
- });
-
- return (
- (response.aggregations?.[SERVICE_NODE_NAME].buckets.map(
- (serviceNodeBucket) => {
- const serviceNodeName = String(serviceNodeBucket.key);
- const hasCGroupData =
- serviceNodeBucket.memory_usage_cgroup.avg.value !== null;
-
- const memoryMetricsKey = hasCGroupData
- ? 'memory_usage_cgroup'
- : 'memory_usage_system';
-
- const cpuUsage =
- // Timeseries is available when isComparisonSearch is true
- 'timeseries' in serviceNodeBucket.cpu_usage
- ? serviceNodeBucket.cpu_usage.timeseries.buckets.map(
- (dateBucket) => ({
- x: dateBucket.key,
- y: dateBucket.avg.value,
- })
- )
- : serviceNodeBucket.cpu_usage.avg.value;
-
- const memoryUsageValue = serviceNodeBucket[memoryMetricsKey];
- const memoryUsage =
- // Timeseries is available when isComparisonSearch is true
- 'timeseries' in memoryUsageValue
- ? memoryUsageValue.timeseries.buckets.map((dateBucket) => ({
- x: dateBucket.key,
- y: dateBucket.avg.value,
- }))
- : serviceNodeBucket[memoryMetricsKey].avg.value;
-
- return {
- serviceNodeName,
- cpuUsage,
- memoryUsage,
- };
- }
- ) as Array>) || []
- );
+ },
}
);
+
+ return (
+ (response.aggregations?.[SERVICE_NODE_NAME].buckets.map(
+ (serviceNodeBucket) => {
+ const serviceNodeName = String(serviceNodeBucket.key);
+ const hasCGroupData =
+ serviceNodeBucket.memory_usage_cgroup.avg.value !== null;
+
+ const memoryMetricsKey = hasCGroupData
+ ? 'memory_usage_cgroup'
+ : 'memory_usage_system';
+
+ const cpuUsage =
+ // Timeseries is available when isComparisonSearch is true
+ 'timeseries' in serviceNodeBucket.cpu_usage
+ ? serviceNodeBucket.cpu_usage.timeseries.buckets.map(
+ (dateBucket) => ({
+ x: dateBucket.key,
+ y: dateBucket.avg.value,
+ })
+ )
+ : serviceNodeBucket.cpu_usage.avg.value;
+
+ const memoryUsageValue = serviceNodeBucket[memoryMetricsKey];
+ const memoryUsage =
+ // Timeseries is available when isComparisonSearch is true
+ 'timeseries' in memoryUsageValue
+ ? memoryUsageValue.timeseries.buckets.map((dateBucket) => ({
+ x: dateBucket.key,
+ y: dateBucket.avg.value,
+ }))
+ : serviceNodeBucket[memoryMetricsKey].avg.value;
+
+ return {
+ serviceNodeName,
+ cpuUsage,
+ memoryUsage,
+ };
+ }
+ ) as Array>) || []
+ );
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts
index ad54a231b52ef..7d9dca9b2a706 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts
@@ -26,7 +26,6 @@ import {
getLatencyValue,
} from '../../helpers/latency_aggregation_type';
import { Setup } from '../../helpers/setup_request';
-import { withApmSpan } from '../../../utils/with_apm_span';
interface ServiceInstanceTransactionPrimaryStatistics {
serviceNodeName: string;
@@ -77,126 +76,124 @@ export async function getServiceInstancesTransactionStatistics<
size?: number;
numBuckets?: number;
}): Promise>> {
- return withApmSpan(
- 'get_service_instances_transaction_statistics',
- async () => {
- const { apmEventClient } = setup;
+ const { apmEventClient } = setup;
- const { intervalString, bucketSize } = getBucketSize({
- start,
- end,
- numBuckets,
- });
+ const { intervalString, bucketSize } = getBucketSize({
+ start,
+ end,
+ numBuckets,
+ });
- const field = getTransactionDurationFieldForAggregatedTransactions(
- searchAggregatedTransactions
- );
+ const field = getTransactionDurationFieldForAggregatedTransactions(
+ searchAggregatedTransactions
+ );
- const subAggs = {
- ...getLatencyAggregation(latencyAggregationType, field),
- failures: {
- filter: {
- term: {
- [EVENT_OUTCOME]: EventOutcome.failure,
- },
- },
+ const subAggs = {
+ ...getLatencyAggregation(latencyAggregationType, field),
+ failures: {
+ filter: {
+ term: {
+ [EVENT_OUTCOME]: EventOutcome.failure,
},
- };
+ },
+ },
+ };
- const query = {
- bool: {
- filter: [
- { term: { [SERVICE_NAME]: serviceName } },
- { term: { [TRANSACTION_TYPE]: transactionType } },
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ...(isComparisonSearch && serviceNodeIds
- ? [{ terms: { [SERVICE_NODE_NAME]: serviceNodeIds } }]
- : []),
- ],
- },
- };
+ const query = {
+ bool: {
+ filter: [
+ { term: { [SERVICE_NAME]: serviceName } },
+ { term: { [TRANSACTION_TYPE]: transactionType } },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ...(isComparisonSearch && serviceNodeIds
+ ? [{ terms: { [SERVICE_NODE_NAME]: serviceNodeIds } }]
+ : []),
+ ],
+ },
+ };
- const aggs = {
- [SERVICE_NODE_NAME]: {
- terms: {
- field: SERVICE_NODE_NAME,
- missing: SERVICE_NODE_NAME_MISSING,
- ...(size ? { size } : {}),
- ...(isComparisonSearch ? { include: serviceNodeIds } : {}),
- },
- aggs: isComparisonSearch
- ? {
- timeseries: {
- date_histogram: {
- field: '@timestamp',
- fixed_interval: intervalString,
- min_doc_count: 0,
- extended_bounds: { min: start, max: end },
- },
- aggs: subAggs,
- },
- }
- : subAggs,
- },
- };
+ const aggs = {
+ [SERVICE_NODE_NAME]: {
+ terms: {
+ field: SERVICE_NODE_NAME,
+ missing: SERVICE_NODE_NAME_MISSING,
+ ...(size ? { size } : {}),
+ ...(isComparisonSearch ? { include: serviceNodeIds } : {}),
+ },
+ aggs: isComparisonSearch
+ ? {
+ timeseries: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: intervalString,
+ min_doc_count: 0,
+ extended_bounds: { min: start, max: end },
+ },
+ aggs: subAggs,
+ },
+ }
+ : subAggs,
+ },
+ };
- const response = await apmEventClient.search({
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ],
- },
- body: { size: 0, query, aggs },
- });
+ const response = await apmEventClient.search(
+ 'get_service_instances_transaction_statistics',
+ {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ],
+ },
+ body: { size: 0, query, aggs },
+ }
+ );
- const bucketSizeInMinutes = bucketSize / 60;
+ const bucketSizeInMinutes = bucketSize / 60;
- return (
- (response.aggregations?.[SERVICE_NODE_NAME].buckets.map(
- (serviceNodeBucket) => {
- const { doc_count: count, key } = serviceNodeBucket;
- const serviceNodeName = String(key);
+ return (
+ (response.aggregations?.[SERVICE_NODE_NAME].buckets.map(
+ (serviceNodeBucket) => {
+ const { doc_count: count, key } = serviceNodeBucket;
+ const serviceNodeName = String(key);
- // Timeseries is returned when isComparisonSearch is true
- if ('timeseries' in serviceNodeBucket) {
- const { timeseries } = serviceNodeBucket;
- return {
- serviceNodeName,
- errorRate: timeseries.buckets.map((dateBucket) => ({
- x: dateBucket.key,
- y: dateBucket.failures.doc_count / dateBucket.doc_count,
- })),
- throughput: timeseries.buckets.map((dateBucket) => ({
- x: dateBucket.key,
- y: dateBucket.doc_count / bucketSizeInMinutes,
- })),
- latency: timeseries.buckets.map((dateBucket) => ({
- x: dateBucket.key,
- y: getLatencyValue({
- aggregation: dateBucket.latency,
- latencyAggregationType,
- }),
- })),
- };
- } else {
- const { failures, latency } = serviceNodeBucket;
- return {
- serviceNodeName,
- errorRate: failures.doc_count / count,
- latency: getLatencyValue({
- aggregation: latency,
- latencyAggregationType,
- }),
- throughput: calculateThroughput({ start, end, value: count }),
- };
- }
- }
- ) as Array>) || []
- );
- }
+ // Timeseries is returned when isComparisonSearch is true
+ if ('timeseries' in serviceNodeBucket) {
+ const { timeseries } = serviceNodeBucket;
+ return {
+ serviceNodeName,
+ errorRate: timeseries.buckets.map((dateBucket) => ({
+ x: dateBucket.key,
+ y: dateBucket.failures.doc_count / dateBucket.doc_count,
+ })),
+ throughput: timeseries.buckets.map((dateBucket) => ({
+ x: dateBucket.key,
+ y: dateBucket.doc_count / bucketSizeInMinutes,
+ })),
+ latency: timeseries.buckets.map((dateBucket) => ({
+ x: dateBucket.key,
+ y: getLatencyValue({
+ aggregation: dateBucket.latency,
+ latencyAggregationType,
+ }),
+ })),
+ };
+ } else {
+ const { failures, latency } = serviceNodeBucket;
+ return {
+ serviceNodeName,
+ errorRate: failures.doc_count / count,
+ latency: getLatencyValue({
+ aggregation: latency,
+ latencyAggregationType,
+ }),
+ throughput: calculateThroughput({ start, end, value: count }),
+ };
+ }
+ }
+ ) as Array>) || []
);
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts b/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts
index e2341b306a878..910725b005411 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts
@@ -25,7 +25,6 @@ import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'
import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
import { should } from './get_service_metadata_icons';
-import { withApmSpan } from '../../utils/with_apm_span';
type ServiceMetadataDetailsRaw = Pick<
TransactionRaw,
@@ -59,7 +58,7 @@ export interface ServiceMetadataDetails {
};
}
-export function getServiceMetadataDetails({
+export async function getServiceMetadataDetails({
serviceName,
setup,
searchAggregatedTransactions,
@@ -68,105 +67,106 @@ export function getServiceMetadataDetails({
setup: Setup & SetupTimeRange;
searchAggregatedTransactions: boolean;
}): Promise {
- return withApmSpan('get_service_metadata_details', async () => {
- const { start, end, apmEventClient } = setup;
+ const { start, end, apmEventClient } = setup;
- const filter = [
- { term: { [SERVICE_NAME]: serviceName } },
- ...rangeQuery(start, end),
- ];
+ const filter = [
+ { term: { [SERVICE_NAME]: serviceName } },
+ ...rangeQuery(start, end),
+ ];
- const params = {
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ProcessorEvent.error,
- ProcessorEvent.metric,
- ],
- },
- body: {
- size: 1,
- _source: [SERVICE, AGENT, HOST, CONTAINER_ID, KUBERNETES, CLOUD],
- query: { bool: { filter, should } },
- aggs: {
- serviceVersions: {
- terms: {
- field: SERVICE_VERSION,
- size: 10,
- order: { _key: 'desc' as const },
- },
+ const params = {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ProcessorEvent.error,
+ ProcessorEvent.metric,
+ ],
+ },
+ body: {
+ size: 1,
+ _source: [SERVICE, AGENT, HOST, CONTAINER_ID, KUBERNETES, CLOUD],
+ query: { bool: { filter, should } },
+ aggs: {
+ serviceVersions: {
+ terms: {
+ field: SERVICE_VERSION,
+ size: 10,
+ order: { _key: 'desc' as const },
},
- availabilityZones: {
- terms: {
- field: CLOUD_AVAILABILITY_ZONE,
- size: 10,
- },
+ },
+ availabilityZones: {
+ terms: {
+ field: CLOUD_AVAILABILITY_ZONE,
+ size: 10,
},
- machineTypes: {
- terms: {
- field: CLOUD_MACHINE_TYPE,
- size: 10,
- },
+ },
+ machineTypes: {
+ terms: {
+ field: CLOUD_MACHINE_TYPE,
+ size: 10,
},
- totalNumberInstances: { cardinality: { field: SERVICE_NODE_NAME } },
},
+ totalNumberInstances: { cardinality: { field: SERVICE_NODE_NAME } },
},
- };
-
- const response = await apmEventClient.search(params);
-
- if (response.hits.total.value === 0) {
- return {
- service: undefined,
- container: undefined,
- cloud: undefined,
- };
- }
+ },
+ };
- const { service, agent, host, kubernetes, container, cloud } = response.hits
- .hits[0]._source as ServiceMetadataDetailsRaw;
+ const response = await apmEventClient.search(
+ 'get_service_metadata_details',
+ params
+ );
- const serviceMetadataDetails = {
- versions: response.aggregations?.serviceVersions.buckets.map(
- (bucket) => bucket.key as string
- ),
- runtime: service.runtime,
- framework: service.framework?.name,
- agent,
+ if (response.hits.total.value === 0) {
+ return {
+ service: undefined,
+ container: undefined,
+ cloud: undefined,
};
+ }
+
+ const { service, agent, host, kubernetes, container, cloud } = response.hits
+ .hits[0]._source as ServiceMetadataDetailsRaw;
- const totalNumberInstances =
- response.aggregations?.totalNumberInstances.value;
+ const serviceMetadataDetails = {
+ versions: response.aggregations?.serviceVersions.buckets.map(
+ (bucket) => bucket.key as string
+ ),
+ runtime: service.runtime,
+ framework: service.framework?.name,
+ agent,
+ };
- const containerDetails =
- host || container || totalNumberInstances || kubernetes
- ? {
- os: host?.os?.platform,
- type: (!!kubernetes ? 'Kubernetes' : 'Docker') as ContainerType,
- isContainerized: !!container?.id,
- totalNumberInstances,
- }
- : undefined;
+ const totalNumberInstances =
+ response.aggregations?.totalNumberInstances.value;
- const cloudDetails = cloud
+ const containerDetails =
+ host || container || totalNumberInstances || kubernetes
? {
- provider: cloud.provider,
- projectName: cloud.project?.name,
- availabilityZones: response.aggregations?.availabilityZones.buckets.map(
- (bucket) => bucket.key as string
- ),
- machineTypes: response.aggregations?.machineTypes.buckets.map(
- (bucket) => bucket.key as string
- ),
+ os: host?.os?.platform,
+ type: (!!kubernetes ? 'Kubernetes' : 'Docker') as ContainerType,
+ isContainerized: !!container?.id,
+ totalNumberInstances,
}
: undefined;
- return {
- service: serviceMetadataDetails,
- container: containerDetails,
- cloud: cloudDetails,
- };
- });
+ const cloudDetails = cloud
+ ? {
+ provider: cloud.provider,
+ projectName: cloud.project?.name,
+ availabilityZones: response.aggregations?.availabilityZones.buckets.map(
+ (bucket) => bucket.key as string
+ ),
+ machineTypes: response.aggregations?.machineTypes.buckets.map(
+ (bucket) => bucket.key as string
+ ),
+ }
+ : undefined;
+
+ return {
+ service: serviceMetadataDetails,
+ container: containerDetails,
+ cloud: cloudDetails,
+ };
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts b/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts
index 94da6545c5e90..469c788a6cf17 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts
@@ -20,7 +20,6 @@ import { rangeQuery } from '../../../server/utils/queries';
import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw';
import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
-import { withApmSpan } from '../../utils/with_apm_span';
type ServiceMetadataIconsRaw = Pick<
TransactionRaw,
@@ -41,7 +40,7 @@ export const should = [
{ exists: { field: AGENT_NAME } },
];
-export function getServiceMetadataIcons({
+export async function getServiceMetadataIcons({
serviceName,
setup,
searchAggregatedTransactions,
@@ -50,55 +49,56 @@ export function getServiceMetadataIcons({
setup: Setup & SetupTimeRange;
searchAggregatedTransactions: boolean;
}): Promise {
- return withApmSpan('get_service_metadata_icons', async () => {
- const { start, end, apmEventClient } = setup;
+ const { start, end, apmEventClient } = setup;
- const filter = [
- { term: { [SERVICE_NAME]: serviceName } },
- ...rangeQuery(start, end),
- ];
+ const filter = [
+ { term: { [SERVICE_NAME]: serviceName } },
+ ...rangeQuery(start, end),
+ ];
- const params = {
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ProcessorEvent.error,
- ProcessorEvent.metric,
- ],
- },
- body: {
- size: 1,
- _source: [KUBERNETES, CLOUD_PROVIDER, CONTAINER_ID, AGENT_NAME],
- query: { bool: { filter, should } },
- },
- };
+ const params = {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ProcessorEvent.error,
+ ProcessorEvent.metric,
+ ],
+ },
+ body: {
+ size: 1,
+ _source: [KUBERNETES, CLOUD_PROVIDER, CONTAINER_ID, AGENT_NAME],
+ query: { bool: { filter, should } },
+ },
+ };
- const response = await apmEventClient.search(params);
+ const response = await apmEventClient.search(
+ 'get_service_metadata_icons',
+ params
+ );
- if (response.hits.total.value === 0) {
- return {
- agentName: undefined,
- containerType: undefined,
- cloudProvider: undefined,
- };
- }
+ if (response.hits.total.value === 0) {
+ return {
+ agentName: undefined,
+ containerType: undefined,
+ cloudProvider: undefined,
+ };
+ }
- const { kubernetes, cloud, container, agent } = response.hits.hits[0]
- ._source as ServiceMetadataIconsRaw;
+ const { kubernetes, cloud, container, agent } = response.hits.hits[0]
+ ._source as ServiceMetadataIconsRaw;
- let containerType: ContainerType;
- if (!!kubernetes) {
- containerType = 'Kubernetes';
- } else if (!!container) {
- containerType = 'Docker';
- }
+ let containerType: ContainerType;
+ if (!!kubernetes) {
+ containerType = 'Kubernetes';
+ } else if (!!container) {
+ containerType = 'Docker';
+ }
- return {
- agentName: agent?.name,
- containerType,
- cloudProvider: cloud?.provider,
- };
- });
+ return {
+ agentName: agent?.name,
+ containerType,
+ cloudProvider: cloud?.provider,
+ };
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_node_metadata.ts b/x-pack/plugins/apm/server/lib/services/get_service_node_metadata.ts
index 8eaf9e96c7fd9..0f0c174179052 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_node_metadata.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_node_metadata.ts
@@ -13,9 +13,8 @@ import {
import { NOT_AVAILABLE_LABEL } from '../../../common/i18n';
import { mergeProjection } from '../../projections/util/merge_projection';
import { getServiceNodesProjection } from '../../projections/service_nodes';
-import { withApmSpan } from '../../utils/with_apm_span';
-export function getServiceNodeMetadata({
+export async function getServiceNodeMetadata({
kuery,
serviceName,
serviceNodeName,
@@ -26,44 +25,44 @@ export function getServiceNodeMetadata({
serviceNodeName: string;
setup: Setup & SetupTimeRange;
}) {
- return withApmSpan('get_service_node_metadata', async () => {
- const { apmEventClient } = setup;
+ const { apmEventClient } = setup;
- const query = mergeProjection(
- getServiceNodesProjection({
- kuery,
- setup,
- serviceName,
- serviceNodeName,
- }),
- {
- body: {
- size: 0,
- aggs: {
- host: {
- terms: {
- field: HOST_NAME,
- size: 1,
- },
+ const query = mergeProjection(
+ getServiceNodesProjection({
+ kuery,
+ setup,
+ serviceName,
+ serviceNodeName,
+ }),
+ {
+ body: {
+ size: 0,
+ aggs: {
+ host: {
+ terms: {
+ field: HOST_NAME,
+ size: 1,
},
- containerId: {
- terms: {
- field: CONTAINER_ID,
- size: 1,
- },
+ },
+ containerId: {
+ terms: {
+ field: CONTAINER_ID,
+ size: 1,
},
},
},
- }
- );
+ },
+ }
+ );
- const response = await apmEventClient.search(query);
+ const response = await apmEventClient.search(
+ 'get_service_node_metadata',
+ query
+ );
- return {
- host: response.aggregations?.host.buckets[0]?.key || NOT_AVAILABLE_LABEL,
- containerId:
- response.aggregations?.containerId.buckets[0]?.key ||
- NOT_AVAILABLE_LABEL,
- };
- });
+ return {
+ host: response.aggregations?.host.buckets[0]?.key || NOT_AVAILABLE_LABEL,
+ containerId:
+ response.aggregations?.containerId.buckets[0]?.key || NOT_AVAILABLE_LABEL,
+ };
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts
index f14dba69bf404..36d372e322cbc 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts
@@ -21,7 +21,6 @@ import {
kqlQuery,
} from '../../../server/utils/queries';
import { Coordinate } from '../../../typings/timeseries';
-import { withApmSpan } from '../../utils/with_apm_span';
import {
getDocumentTypeFilterForAggregatedTransactions,
getProcessorEventForAggregatedTransactions,
@@ -68,74 +67,72 @@ export async function getServiceTransactionGroupDetailedStatistics({
impact: number;
}>
> {
- return withApmSpan(
- 'get_service_transaction_group_detailed_statistics',
- async () => {
- const { apmEventClient } = setup;
- const { intervalString } = getBucketSize({ start, end, numBuckets });
+ const { apmEventClient } = setup;
+ const { intervalString } = getBucketSize({ start, end, numBuckets });
- const field = getTransactionDurationFieldForAggregatedTransactions(
- searchAggregatedTransactions
- );
+ const field = getTransactionDurationFieldForAggregatedTransactions(
+ searchAggregatedTransactions
+ );
- const response = await apmEventClient.search({
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ],
+ const response = await apmEventClient.search(
+ 'get_service_transaction_group_detailed_statistics',
+ {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ { term: { [SERVICE_NAME]: serviceName } },
+ { term: { [TRANSACTION_TYPE]: transactionType } },
+ ...getDocumentTypeFilterForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ],
+ },
},
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- { term: { [SERVICE_NAME]: serviceName } },
- { term: { [TRANSACTION_TYPE]: transactionType } },
- ...getDocumentTypeFilterForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ],
+ aggs: {
+ total_duration: { sum: { field } },
+ transaction_groups: {
+ terms: {
+ field: TRANSACTION_NAME,
+ include: transactionNames,
+ size: transactionNames.length,
},
- },
- aggs: {
- total_duration: { sum: { field } },
- transaction_groups: {
- terms: {
- field: TRANSACTION_NAME,
- include: transactionNames,
- size: transactionNames.length,
+ aggs: {
+ transaction_group_total_duration: {
+ sum: { field },
},
- aggs: {
- transaction_group_total_duration: {
- sum: { field },
+ timeseries: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: intervalString,
+ min_doc_count: 0,
+ extended_bounds: {
+ min: start,
+ max: end,
+ },
},
- timeseries: {
- date_histogram: {
- field: '@timestamp',
- fixed_interval: intervalString,
- min_doc_count: 0,
- extended_bounds: {
- min: start,
- max: end,
+ aggs: {
+ throughput_rate: {
+ rate: {
+ unit: 'minute',
},
},
- aggs: {
- throughput_rate: {
- rate: {
- unit: 'minute',
- },
- },
- ...getLatencyAggregation(latencyAggregationType, field),
- [EVENT_OUTCOME]: {
- terms: {
- field: EVENT_OUTCOME,
- include: [EventOutcome.failure, EventOutcome.success],
- },
+ ...getLatencyAggregation(latencyAggregationType, field),
+ [EVENT_OUTCOME]: {
+ terms: {
+ field: EVENT_OUTCOME,
+ include: [EventOutcome.failure, EventOutcome.success],
},
},
},
@@ -143,46 +140,42 @@ export async function getServiceTransactionGroupDetailedStatistics({
},
},
},
- });
-
- const buckets = response.aggregations?.transaction_groups.buckets ?? [];
-
- const totalDuration = response.aggregations?.total_duration.value;
- return buckets.map((bucket) => {
- const transactionName = bucket.key as string;
- const latency = bucket.timeseries.buckets.map((timeseriesBucket) => ({
- x: timeseriesBucket.key,
- y: getLatencyValue({
- latencyAggregationType,
- aggregation: timeseriesBucket.latency,
- }),
- }));
- const throughput = bucket.timeseries.buckets.map(
- (timeseriesBucket) => ({
- x: timeseriesBucket.key,
- y: timeseriesBucket.throughput_rate.value,
- })
- );
- const errorRate = bucket.timeseries.buckets.map((timeseriesBucket) => ({
- x: timeseriesBucket.key,
- y: calculateTransactionErrorPercentage(
- timeseriesBucket[EVENT_OUTCOME]
- ),
- }));
- const transactionGroupTotalDuration =
- bucket.transaction_group_total_duration.value || 0;
- return {
- transactionName,
- latency,
- throughput,
- errorRate,
- impact: totalDuration
- ? (transactionGroupTotalDuration * 100) / totalDuration
- : 0,
- };
- });
+ },
}
);
+
+ const buckets = response.aggregations?.transaction_groups.buckets ?? [];
+
+ const totalDuration = response.aggregations?.total_duration.value;
+ return buckets.map((bucket) => {
+ const transactionName = bucket.key as string;
+ const latency = bucket.timeseries.buckets.map((timeseriesBucket) => ({
+ x: timeseriesBucket.key,
+ y: getLatencyValue({
+ latencyAggregationType,
+ aggregation: timeseriesBucket.latency,
+ }),
+ }));
+ const throughput = bucket.timeseries.buckets.map((timeseriesBucket) => ({
+ x: timeseriesBucket.key,
+ y: timeseriesBucket.throughput_rate.value,
+ }));
+ const errorRate = bucket.timeseries.buckets.map((timeseriesBucket) => ({
+ x: timeseriesBucket.key,
+ y: calculateTransactionErrorPercentage(timeseriesBucket[EVENT_OUTCOME]),
+ }));
+ const transactionGroupTotalDuration =
+ bucket.transaction_group_total_duration.value || 0;
+ return {
+ transactionName,
+ latency,
+ throughput,
+ errorRate,
+ impact: totalDuration
+ ? (transactionGroupTotalDuration * 100) / totalDuration
+ : 0,
+ };
+ });
}
export async function getServiceTransactionGroupDetailedStatisticsPeriods({
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts
index 28574bab4df21..a4cc27c875d73 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts
@@ -18,7 +18,6 @@ import {
rangeQuery,
kqlQuery,
} from '../../../server/utils/queries';
-import { withApmSpan } from '../../utils/with_apm_span';
import {
getDocumentTypeFilterForAggregatedTransactions,
getProcessorEventForAggregatedTransactions,
@@ -56,14 +55,15 @@ export async function getServiceTransactionGroups({
transactionType: string;
latencyAggregationType: LatencyAggregationType;
}) {
- return withApmSpan('get_service_transaction_groups', async () => {
- const { apmEventClient, start, end } = setup;
+ const { apmEventClient, start, end } = setup;
- const field = getTransactionDurationFieldForAggregatedTransactions(
- searchAggregatedTransactions
- );
+ const field = getTransactionDurationFieldForAggregatedTransactions(
+ searchAggregatedTransactions
+ );
- const response = await apmEventClient.search({
+ const response = await apmEventClient.search(
+ 'get_service_transaction_groups',
+ {
apm: {
events: [
getProcessorEventForAggregatedTransactions(
@@ -110,45 +110,45 @@ export async function getServiceTransactionGroups({
},
},
},
- });
+ }
+ );
- const totalDuration = response.aggregations?.total_duration.value;
+ const totalDuration = response.aggregations?.total_duration.value;
- const transactionGroups =
- response.aggregations?.transaction_groups.buckets.map((bucket) => {
- const errorRate = calculateTransactionErrorPercentage(
- bucket[EVENT_OUTCOME]
- );
+ const transactionGroups =
+ response.aggregations?.transaction_groups.buckets.map((bucket) => {
+ const errorRate = calculateTransactionErrorPercentage(
+ bucket[EVENT_OUTCOME]
+ );
- const transactionGroupTotalDuration =
- bucket.transaction_group_total_duration.value || 0;
+ const transactionGroupTotalDuration =
+ bucket.transaction_group_total_duration.value || 0;
- return {
- name: bucket.key as string,
- latency: getLatencyValue({
- latencyAggregationType,
- aggregation: bucket.latency,
- }),
- throughput: calculateThroughput({
- start,
- end,
- value: bucket.doc_count,
- }),
- errorRate,
- impact: totalDuration
- ? (transactionGroupTotalDuration * 100) / totalDuration
- : 0,
- };
- }) ?? [];
+ return {
+ name: bucket.key as string,
+ latency: getLatencyValue({
+ latencyAggregationType,
+ aggregation: bucket.latency,
+ }),
+ throughput: calculateThroughput({
+ start,
+ end,
+ value: bucket.doc_count,
+ }),
+ errorRate,
+ impact: totalDuration
+ ? (transactionGroupTotalDuration * 100) / totalDuration
+ : 0,
+ };
+ }) ?? [];
- return {
- transactionGroups: transactionGroups.map((transactionGroup) => ({
- ...transactionGroup,
- transactionType,
- })),
- isAggregationAccurate:
- (response.aggregations?.transaction_groups.sum_other_doc_count ?? 0) ===
- 0,
- };
- });
+ return {
+ transactionGroups: transactionGroups.map((transactionGroup) => ({
+ ...transactionGroup,
+ transactionType,
+ })),
+ isAggregationAccurate:
+ (response.aggregations?.transaction_groups.sum_other_doc_count ?? 0) ===
+ 0,
+ };
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts
index e280ab6db1665..f38a7fba09d96 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_types.ts
@@ -15,9 +15,8 @@ import {
getDocumentTypeFilterForAggregatedTransactions,
getProcessorEventForAggregatedTransactions,
} from '../helpers/aggregated_transactions';
-import { withApmSpan } from '../../utils/with_apm_span';
-export function getServiceTransactionTypes({
+export async function getServiceTransactionTypes({
setup,
serviceName,
searchAggregatedTransactions,
@@ -26,41 +25,42 @@ export function getServiceTransactionTypes({
setup: Setup & SetupTimeRange;
searchAggregatedTransactions: boolean;
}) {
- return withApmSpan('get_service_transaction_types', async () => {
- const { start, end, apmEventClient } = setup;
+ const { start, end, apmEventClient } = setup;
- const params = {
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- ...getDocumentTypeFilterForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- { term: { [SERVICE_NAME]: serviceName } },
- ...rangeQuery(start, end),
- ],
- },
+ const params = {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ ...getDocumentTypeFilterForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ { term: { [SERVICE_NAME]: serviceName } },
+ ...rangeQuery(start, end),
+ ],
},
- aggs: {
- types: {
- terms: { field: TRANSACTION_TYPE, size: 100 },
- },
+ },
+ aggs: {
+ types: {
+ terms: { field: TRANSACTION_TYPE, size: 100 },
},
},
- };
+ },
+ };
- const { aggregations } = await apmEventClient.search(params);
- const transactionTypes =
- aggregations?.types.buckets.map((bucket) => bucket.key as string) || [];
- return { transactionTypes };
- });
+ const { aggregations } = await apmEventClient.search(
+ 'get_service_transaction_types',
+ params
+ );
+ const transactionTypes =
+ aggregations?.types.buckets.map((bucket) => bucket.key as string) || [];
+ return { transactionTypes };
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts
index b42fd340bfb42..f33bedb6ef4fb 100644
--- a/x-pack/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts
@@ -9,35 +9,31 @@ import { rangeQuery } from '../../../../server/utils/queries';
import { ProcessorEvent } from '../../../../common/processor_event';
import { OBSERVER_VERSION_MAJOR } from '../../../../common/elasticsearch_fieldnames';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
-import { withApmSpan } from '../../../utils/with_apm_span';
// returns true if 6.x data is found
export async function getLegacyDataStatus(setup: Setup & SetupTimeRange) {
- return withApmSpan('get_legacy_data_status', async () => {
- const { apmEventClient, start, end } = setup;
+ const { apmEventClient, start, end } = setup;
- const params = {
- terminateAfter: 1,
- apm: {
- events: [ProcessorEvent.transaction],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- { range: { [OBSERVER_VERSION_MAJOR]: { lt: 7 } } },
- ...rangeQuery(start, end),
- ],
- },
+ const params = {
+ terminateAfter: 1,
+ apm: {
+ events: [ProcessorEvent.transaction],
+ includeLegacyData: true,
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ { range: { [OBSERVER_VERSION_MAJOR]: { lt: 7 } } },
+ ...rangeQuery(start, end),
+ ],
},
},
- };
+ },
+ };
- const resp = await apmEventClient.search(params, {
- includeLegacyData: true,
- });
- const hasLegacyData = resp.hits.total.value > 0;
- return hasLegacyData;
- });
+ const resp = await apmEventClient.search('get_legacy_data_status', params);
+ const hasLegacyData = resp.hits.total.value > 0;
+ return hasLegacyData;
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts
index 1e36df379e964..019ab8770887a 100644
--- a/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_services/get_service_transaction_stats.ts
@@ -33,7 +33,6 @@ import {
getOutcomeAggregation,
} from '../../helpers/transaction_error_rate';
import { ServicesItemsSetup } from './get_services_items';
-import { withApmSpan } from '../../../utils/with_apm_span';
interface AggregationParams {
environment?: string;
@@ -50,23 +49,24 @@ export async function getServiceTransactionStats({
searchAggregatedTransactions,
maxNumServices,
}: AggregationParams) {
- return withApmSpan('get_service_transaction_stats', async () => {
- const { apmEventClient, start, end } = setup;
+ const { apmEventClient, start, end } = setup;
- const outcomes = getOutcomeAggregation();
+ const outcomes = getOutcomeAggregation();
- const metrics = {
- avg_duration: {
- avg: {
- field: getTransactionDurationFieldForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- },
+ const metrics = {
+ avg_duration: {
+ avg: {
+ field: getTransactionDurationFieldForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
},
- outcomes,
- };
+ },
+ outcomes,
+ };
- const response = await apmEventClient.search({
+ const response = await apmEventClient.search(
+ 'get_service_transaction_stats',
+ {
apm: {
events: [
getProcessorEventForAggregatedTransactions(
@@ -133,64 +133,64 @@ export async function getServiceTransactionStats({
},
},
},
- });
+ }
+ );
- return (
- response.aggregations?.services.buckets.map((bucket) => {
- const topTransactionTypeBucket =
- bucket.transactionType.buckets.find(
- ({ key }) =>
- key === TRANSACTION_REQUEST || key === TRANSACTION_PAGE_LOAD
- ) ?? bucket.transactionType.buckets[0];
+ return (
+ response.aggregations?.services.buckets.map((bucket) => {
+ const topTransactionTypeBucket =
+ bucket.transactionType.buckets.find(
+ ({ key }) =>
+ key === TRANSACTION_REQUEST || key === TRANSACTION_PAGE_LOAD
+ ) ?? bucket.transactionType.buckets[0];
- return {
- serviceName: bucket.key as string,
- transactionType: topTransactionTypeBucket.key as string,
- environments: topTransactionTypeBucket.environments.buckets.map(
- (environmentBucket) => environmentBucket.key as string
+ return {
+ serviceName: bucket.key as string,
+ transactionType: topTransactionTypeBucket.key as string,
+ environments: topTransactionTypeBucket.environments.buckets.map(
+ (environmentBucket) => environmentBucket.key as string
+ ),
+ agentName: topTransactionTypeBucket.sample.top[0].metrics[
+ AGENT_NAME
+ ] as AgentName,
+ avgResponseTime: {
+ value: topTransactionTypeBucket.avg_duration.value,
+ timeseries: topTransactionTypeBucket.timeseries.buckets.map(
+ (dateBucket) => ({
+ x: dateBucket.key,
+ y: dateBucket.avg_duration.value,
+ })
),
- agentName: topTransactionTypeBucket.sample.top[0].metrics[
- AGENT_NAME
- ] as AgentName,
- avgResponseTime: {
- value: topTransactionTypeBucket.avg_duration.value,
- timeseries: topTransactionTypeBucket.timeseries.buckets.map(
- (dateBucket) => ({
- x: dateBucket.key,
- y: dateBucket.avg_duration.value,
- })
- ),
- },
- transactionErrorRate: {
- value: calculateTransactionErrorPercentage(
- topTransactionTypeBucket.outcomes
- ),
- timeseries: topTransactionTypeBucket.timeseries.buckets.map(
- (dateBucket) => ({
- x: dateBucket.key,
- y: calculateTransactionErrorPercentage(dateBucket.outcomes),
- })
- ),
- },
- transactionsPerMinute: {
- value: calculateThroughput({
- start,
- end,
- value: topTransactionTypeBucket.doc_count,
- }),
- timeseries: topTransactionTypeBucket.timeseries.buckets.map(
- (dateBucket) => ({
- x: dateBucket.key,
- y: calculateThroughput({
- start,
- end,
- value: dateBucket.doc_count,
- }),
- })
- ),
- },
- };
- }) ?? []
- );
- });
+ },
+ transactionErrorRate: {
+ value: calculateTransactionErrorPercentage(
+ topTransactionTypeBucket.outcomes
+ ),
+ timeseries: topTransactionTypeBucket.timeseries.buckets.map(
+ (dateBucket) => ({
+ x: dateBucket.key,
+ y: calculateTransactionErrorPercentage(dateBucket.outcomes),
+ })
+ ),
+ },
+ transactionsPerMinute: {
+ value: calculateThroughput({
+ start,
+ end,
+ value: topTransactionTypeBucket.doc_count,
+ }),
+ timeseries: topTransactionTypeBucket.timeseries.buckets.map(
+ (dateBucket) => ({
+ x: dateBucket.key,
+ y: calculateThroughput({
+ start,
+ end,
+ value: dateBucket.doc_count,
+ }),
+ })
+ ),
+ },
+ };
+ }) ?? []
+ );
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_from_metric_documents.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_from_metric_documents.ts
index 906cc62e64d1a..4692d1122b16c 100644
--- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_from_metric_documents.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_from_metric_documents.ts
@@ -14,9 +14,8 @@ import {
import { environmentQuery, kqlQuery, rangeQuery } from '../../../utils/queries';
import { ProcessorEvent } from '../../../../common/processor_event';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
-import { withApmSpan } from '../../../utils/with_apm_span';
-export function getServicesFromMetricDocuments({
+export async function getServicesFromMetricDocuments({
environment,
setup,
maxNumServices,
@@ -27,10 +26,11 @@ export function getServicesFromMetricDocuments({
maxNumServices: number;
kuery?: string;
}) {
- return withApmSpan('get_services_from_metric_documents', async () => {
- const { apmEventClient, start, end } = setup;
+ const { apmEventClient, start, end } = setup;
- const response = await apmEventClient.search({
+ const response = await apmEventClient.search(
+ 'get_services_from_metric_documents',
+ {
apm: {
events: [ProcessorEvent.metric],
},
@@ -67,18 +67,18 @@ export function getServicesFromMetricDocuments({
},
},
},
- });
+ }
+ );
- return (
- response.aggregations?.services.buckets.map((bucket) => {
- return {
- serviceName: bucket.key as string,
- environments: bucket.environments.buckets.map(
- (envBucket) => envBucket.key as string
- ),
- agentName: bucket.latest.top[0].metrics[AGENT_NAME] as AgentName,
- };
- }) ?? []
- );
- });
+ return (
+ response.aggregations?.services.buckets.map((bucket) => {
+ return {
+ serviceName: bucket.key as string,
+ environments: bucket.environments.buckets.map(
+ (envBucket) => envBucket.key as string
+ ),
+ agentName: bucket.latest.top[0].metrics[AGENT_NAME] as AgentName,
+ };
+ }) ?? []
+ );
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_services/has_historical_agent_data.ts b/x-pack/plugins/apm/server/lib/services/get_services/has_historical_agent_data.ts
index 28f6944fd24da..97b8a8fa5505b 100644
--- a/x-pack/plugins/apm/server/lib/services/get_services/has_historical_agent_data.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_services/has_historical_agent_data.ts
@@ -6,29 +6,26 @@
*/
import { ProcessorEvent } from '../../../../common/processor_event';
-import { withApmSpan } from '../../../utils/with_apm_span';
import { Setup } from '../../helpers/setup_request';
// Note: this logic is duplicated in tutorials/apm/envs/on_prem
export async function hasHistoricalAgentData(setup: Setup) {
- return withApmSpan('has_historical_agent_data', async () => {
- const { apmEventClient } = setup;
+ const { apmEventClient } = setup;
- const params = {
- terminateAfter: 1,
- apm: {
- events: [
- ProcessorEvent.error,
- ProcessorEvent.metric,
- ProcessorEvent.transaction,
- ],
- },
- body: {
- size: 0,
- },
- };
+ const params = {
+ terminateAfter: 1,
+ apm: {
+ events: [
+ ProcessorEvent.error,
+ ProcessorEvent.metric,
+ ProcessorEvent.transaction,
+ ],
+ },
+ body: {
+ size: 0,
+ },
+ };
- const resp = await apmEventClient.search(params);
- return resp.hits.total.value > 0;
- });
+ const resp = await apmEventClient.search('has_historical_agent_data', params);
+ return resp.hits.total.value > 0;
}
diff --git a/x-pack/plugins/apm/server/lib/services/get_throughput.ts b/x-pack/plugins/apm/server/lib/services/get_throughput.ts
index 5f5008a28c232..b0cb917d302fc 100644
--- a/x-pack/plugins/apm/server/lib/services/get_throughput.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_throughput.ts
@@ -21,7 +21,6 @@ import {
} from '../helpers/aggregated_transactions';
import { getBucketSize } from '../helpers/get_bucket_size';
import { Setup } from '../helpers/setup_request';
-import { withApmSpan } from '../../utils/with_apm_span';
interface Options {
environment?: string;
@@ -88,20 +87,18 @@ function fetcher({
},
};
- return apmEventClient.search(params);
+ return apmEventClient.search('get_throughput_for_service', params);
}
-export function getThroughput(options: Options) {
- return withApmSpan('get_throughput_for_service', async () => {
- const response = await fetcher(options);
+export async function getThroughput(options: Options) {
+ const response = await fetcher(options);
- return (
- response.aggregations?.timeseries.buckets.map((bucket) => {
- return {
- x: bucket.key,
- y: bucket.throughput.value,
- };
- }) ?? []
- );
- });
+ return (
+ response.aggregations?.timeseries.buckets.map((bucket) => {
+ return {
+ x: bucket.key,
+ y: bucket.throughput.value,
+ };
+ }) ?? []
+ );
}
diff --git a/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_statistics.ts b/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_statistics.ts
index 858f36e6e2c13..bb98abf724db4 100644
--- a/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_statistics.ts
+++ b/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_statistics.ts
@@ -41,7 +41,7 @@ const maybeAdd = (to: any[], value: any) => {
to.push(value);
};
-function getProfilingStats({
+async function getProfilingStats({
apmEventClient,
filter,
valueTypeField,
@@ -50,49 +50,47 @@ function getProfilingStats({
filter: ESFilter[];
valueTypeField: string;
}) {
- return withApmSpan('get_profiling_stats', async () => {
- const response = await apmEventClient.search({
- apm: {
- events: [ProcessorEvent.profile],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter,
- },
+ const response = await apmEventClient.search('get_profiling_stats', {
+ apm: {
+ events: [ProcessorEvent.profile],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter,
},
- aggs: {
- stacks: {
- terms: {
- field: PROFILE_TOP_ID,
- size: MAX_STACK_IDS,
- order: {
- value: 'desc',
- },
+ },
+ aggs: {
+ stacks: {
+ terms: {
+ field: PROFILE_TOP_ID,
+ size: MAX_STACK_IDS,
+ order: {
+ value: 'desc',
},
- aggs: {
- value: {
- sum: {
- field: valueTypeField,
- },
+ },
+ aggs: {
+ value: {
+ sum: {
+ field: valueTypeField,
},
},
},
},
},
- });
+ },
+ });
- const stacks =
- response.aggregations?.stacks.buckets.map((stack) => {
- return {
- id: stack.key as string,
- value: stack.value.value!,
- };
- }) ?? [];
+ const stacks =
+ response.aggregations?.stacks.buckets.map((stack) => {
+ return {
+ id: stack.key as string,
+ value: stack.value.value!,
+ };
+ }) ?? [];
- return stacks;
- });
+ return stacks;
}
function getProfilesWithStacks({
@@ -103,8 +101,9 @@ function getProfilesWithStacks({
filter: ESFilter[];
}) {
return withApmSpan('get_profiles_with_stacks', async () => {
- const cardinalityResponse = await withApmSpan('get_top_cardinality', () =>
- apmEventClient.search({
+ const cardinalityResponse = await apmEventClient.search(
+ 'get_top_cardinality',
+ {
apm: {
events: [ProcessorEvent.profile],
},
@@ -121,7 +120,7 @@ function getProfilesWithStacks({
},
},
},
- })
+ }
);
const cardinality = cardinalityResponse.aggregations?.top.value ?? 0;
@@ -140,39 +139,37 @@ function getProfilesWithStacks({
const allResponses = await withApmSpan('get_all_stacks', async () => {
return Promise.all(
[...new Array(partitions)].map(async (_, num) => {
- const response = await withApmSpan('get_partition', () =>
- apmEventClient.search({
- apm: {
- events: [ProcessorEvent.profile],
- },
- body: {
- query: {
- bool: {
- filter,
- },
+ const response = await apmEventClient.search('get_partition', {
+ apm: {
+ events: [ProcessorEvent.profile],
+ },
+ body: {
+ query: {
+ bool: {
+ filter,
},
- aggs: {
- top: {
- terms: {
- field: PROFILE_TOP_ID,
- size: Math.max(MAX_STACKS_PER_REQUEST),
- include: {
- num_partitions: partitions,
- partition: num,
- },
+ },
+ aggs: {
+ top: {
+ terms: {
+ field: PROFILE_TOP_ID,
+ size: Math.max(MAX_STACKS_PER_REQUEST),
+ include: {
+ num_partitions: partitions,
+ partition: num,
},
- aggs: {
- latest: {
- top_hits: {
- _source: [PROFILE_TOP_ID, PROFILE_STACK],
- },
+ },
+ aggs: {
+ latest: {
+ top_hits: {
+ _source: [PROFILE_TOP_ID, PROFILE_STACK],
},
},
},
},
},
- })
- );
+ },
+ });
return (
response.aggregations?.top.buckets.flatMap((bucket) => {
diff --git a/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_timeline.ts b/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_timeline.ts
index 93fa029da8c72..af3cd6596a8c1 100644
--- a/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_timeline.ts
+++ b/x-pack/plugins/apm/server/lib/services/profiling/get_service_profiling_timeline.ts
@@ -17,7 +17,6 @@ import {
} from '../../../../common/profiling';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
import { getBucketSize } from '../../helpers/get_bucket_size';
-import { withApmSpan } from '../../../utils/with_apm_span';
import { kqlQuery } from '../../../utils/queries';
const configMap = mapValues(
@@ -38,10 +37,11 @@ export async function getServiceProfilingTimeline({
setup: Setup & SetupTimeRange;
environment?: string;
}) {
- return withApmSpan('get_service_profiling_timeline', async () => {
- const { apmEventClient, start, end } = setup;
+ const { apmEventClient, start, end } = setup;
- const response = await apmEventClient.search({
+ const response = await apmEventClient.search(
+ 'get_service_profiling_timeline',
+ {
apm: {
events: [ProcessorEvent.profile],
},
@@ -96,29 +96,29 @@ export async function getServiceProfilingTimeline({
},
},
},
- });
+ }
+ );
- const { aggregations } = response;
+ const { aggregations } = response;
- if (!aggregations) {
- return [];
- }
+ if (!aggregations) {
+ return [];
+ }
- return aggregations.timeseries.buckets.map((bucket) => {
- return {
- x: bucket.key,
- valueTypes: {
- unknown: bucket.value_type.buckets.unknown.num_profiles.value,
- // TODO: use enum as object key. not possible right now
- // because of https://github.com/microsoft/TypeScript/issues/37888
- ...mapValues(configMap, (_, key) => {
- return (
- bucket.value_type.buckets[key as ProfilingValueType]?.num_profiles
- .value ?? 0
- );
- }),
- },
- };
- });
+ return aggregations.timeseries.buckets.map((bucket) => {
+ return {
+ x: bucket.key,
+ valueTypes: {
+ unknown: bucket.value_type.buckets.unknown.num_profiles.value,
+ // TODO: use enum as object key. not possible right now
+ // because of https://github.com/microsoft/TypeScript/issues/37888
+ ...mapValues(configMap, (_, key) => {
+ return (
+ bucket.value_type.buckets[key as ProfilingValueType]?.num_profiles
+ .value ?? 0
+ );
+ }),
+ },
+ };
});
}
diff --git a/x-pack/plugins/apm/server/lib/services/queries.test.ts b/x-pack/plugins/apm/server/lib/services/queries.test.ts
index b167eff65ee0a..6adaca9c1a93d 100644
--- a/x-pack/plugins/apm/server/lib/services/queries.test.ts
+++ b/x-pack/plugins/apm/server/lib/services/queries.test.ts
@@ -55,7 +55,7 @@ describe('services queries', () => {
})
);
- const allParams = mock.spy.mock.calls.map((call) => call[0]);
+ const allParams = mock.spy.mock.calls.map((call) => call[1]);
expect(allParams).toMatchSnapshot();
});
diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts
index 1885382435562..c112c3be3362b 100644
--- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts
+++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts
@@ -12,7 +12,6 @@ import {
AgentConfigurationIntake,
} from '../../../../common/agent_configuration/configuration_types';
import { APMIndexDocumentParams } from '../../helpers/create_es_client/create_internal_es_client';
-import { withApmSpan } from '../../../utils/with_apm_span';
export function createOrUpdateConfiguration({
configurationId,
@@ -23,30 +22,28 @@ export function createOrUpdateConfiguration({
configurationIntake: AgentConfigurationIntake;
setup: Setup;
}) {
- return withApmSpan('create_or_update_configuration', async () => {
- const { internalClient, indices } = setup;
+ const { internalClient, indices } = setup;
- const params: APMIndexDocumentParams = {
- refresh: true,
- index: indices.apmAgentConfigurationIndex,
- body: {
- agent_name: configurationIntake.agent_name,
- service: {
- name: configurationIntake.service.name,
- environment: configurationIntake.service.environment,
- },
- settings: configurationIntake.settings,
- '@timestamp': Date.now(),
- applied_by_agent: false,
- etag: hash(configurationIntake),
+ const params: APMIndexDocumentParams = {
+ refresh: true,
+ index: indices.apmAgentConfigurationIndex,
+ body: {
+ agent_name: configurationIntake.agent_name,
+ service: {
+ name: configurationIntake.service.name,
+ environment: configurationIntake.service.environment,
},
- };
+ settings: configurationIntake.settings,
+ '@timestamp': Date.now(),
+ applied_by_agent: false,
+ etag: hash(configurationIntake),
+ },
+ };
- // by specifying an id elasticsearch will delete the previous doc and insert the updated doc
- if (configurationId) {
- params.id = configurationId;
- }
+ // by specifying an id elasticsearch will delete the previous doc and insert the updated doc
+ if (configurationId) {
+ params.id = configurationId;
+ }
- return internalClient.index(params);
- });
+ return internalClient.index('create_or_update_agent_configuration', params);
}
diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts
index 6ed6f79979889..125c97730a6fa 100644
--- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts
+++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-import { withApmSpan } from '../../../utils/with_apm_span';
import { Setup } from '../../helpers/setup_request';
export async function deleteConfiguration({
@@ -15,15 +14,13 @@ export async function deleteConfiguration({
configurationId: string;
setup: Setup;
}) {
- return withApmSpan('delete_agent_configuration', async () => {
- const { internalClient, indices } = setup;
+ const { internalClient, indices } = setup;
- const params = {
- refresh: 'wait_for' as const,
- index: indices.apmAgentConfigurationIndex,
- id: configurationId,
- };
+ const params = {
+ refresh: 'wait_for' as const,
+ index: indices.apmAgentConfigurationIndex,
+ id: configurationId,
+ };
- return internalClient.delete(params);
- });
+ return internalClient.delete('delete_agent_configuration', params);
}
diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts
index 9fd4849c7640a..3543d38f7b5d1 100644
--- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts
+++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts
@@ -11,47 +11,45 @@ import {
SERVICE_ENVIRONMENT,
SERVICE_NAME,
} from '../../../../common/elasticsearch_fieldnames';
-import { withApmSpan } from '../../../utils/with_apm_span';
import { Setup } from '../../helpers/setup_request';
import { convertConfigSettingsToString } from './convert_settings_to_string';
-export function findExactConfiguration({
+export async function findExactConfiguration({
service,
setup,
}: {
service: AgentConfiguration['service'];
setup: Setup;
}) {
- return withApmSpan('find_exact_agent_configuration', async () => {
- const { internalClient, indices } = setup;
-
- const serviceNameFilter = service.name
- ? { term: { [SERVICE_NAME]: service.name } }
- : { bool: { must_not: [{ exists: { field: SERVICE_NAME } }] } };
-
- const environmentFilter = service.environment
- ? { term: { [SERVICE_ENVIRONMENT]: service.environment } }
- : { bool: { must_not: [{ exists: { field: SERVICE_ENVIRONMENT } }] } };
-
- const params = {
- index: indices.apmAgentConfigurationIndex,
- body: {
- query: {
- bool: { filter: [serviceNameFilter, environmentFilter] },
- },
+ const { internalClient, indices } = setup;
+
+ const serviceNameFilter = service.name
+ ? { term: { [SERVICE_NAME]: service.name } }
+ : { bool: { must_not: [{ exists: { field: SERVICE_NAME } }] } };
+
+ const environmentFilter = service.environment
+ ? { term: { [SERVICE_ENVIRONMENT]: service.environment } }
+ : { bool: { must_not: [{ exists: { field: SERVICE_ENVIRONMENT } }] } };
+
+ const params = {
+ index: indices.apmAgentConfigurationIndex,
+ body: {
+ query: {
+ bool: { filter: [serviceNameFilter, environmentFilter] },
},
- };
+ },
+ };
- const resp = await internalClient.search(
- params
- );
+ const resp = await internalClient.search(
+ 'find_exact_agent_configuration',
+ params
+ );
- const hit = resp.hits.hits[0] as SearchHit | undefined;
+ const hit = resp.hits.hits[0] as SearchHit | undefined;
- if (!hit) {
- return;
- }
+ if (!hit) {
+ return;
+ }
- return convertConfigSettingsToString(hit);
- });
+ return convertConfigSettingsToString(hit);
}
diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts
index 379ed12e37389..0b6dd10b42e25 100644
--- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts
+++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts
@@ -9,7 +9,6 @@ import { ProcessorEvent } from '../../../../common/processor_event';
import { Setup } from '../../helpers/setup_request';
import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames';
import { AGENT_NAME } from '../../../../common/elasticsearch_fieldnames';
-import { withApmSpan } from '../../../utils/with_apm_span';
export async function getAgentNameByService({
serviceName,
@@ -18,35 +17,36 @@ export async function getAgentNameByService({
serviceName: string;
setup: Setup;
}) {
- return withApmSpan('get_agent_name_by_service', async () => {
- const { apmEventClient } = setup;
+ const { apmEventClient } = setup;
- const params = {
- terminateAfter: 1,
- apm: {
- events: [
- ProcessorEvent.transaction,
- ProcessorEvent.error,
- ProcessorEvent.metric,
- ],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter: [{ term: { [SERVICE_NAME]: serviceName } }],
- },
+ const params = {
+ terminateAfter: 1,
+ apm: {
+ events: [
+ ProcessorEvent.transaction,
+ ProcessorEvent.error,
+ ProcessorEvent.metric,
+ ],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [{ term: { [SERVICE_NAME]: serviceName } }],
},
- aggs: {
- agent_names: {
- terms: { field: AGENT_NAME, size: 1 },
- },
+ },
+ aggs: {
+ agent_names: {
+ terms: { field: AGENT_NAME, size: 1 },
},
},
- };
+ },
+ };
- const { aggregations } = await apmEventClient.search(params);
- const agentName = aggregations?.agent_names.buckets[0]?.key;
- return agentName as string | undefined;
- });
+ const { aggregations } = await apmEventClient.search(
+ 'get_agent_name_by_service',
+ params
+ );
+ const agentName = aggregations?.agent_names.buckets[0]?.key;
+ return agentName as string | undefined;
}
diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts
index 4a32b3c3a370b..124a373d3cf07 100644
--- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts
+++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-import { withApmSpan } from '../../../../utils/with_apm_span';
import { Setup } from '../../../helpers/setup_request';
import {
SERVICE_NAME,
@@ -20,36 +19,37 @@ export async function getExistingEnvironmentsForService({
serviceName: string | undefined;
setup: Setup;
}) {
- return withApmSpan('get_existing_environments_for_service', async () => {
- const { internalClient, indices, config } = setup;
- const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments'];
+ const { internalClient, indices, config } = setup;
+ const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments'];
- const bool = serviceName
- ? { filter: [{ term: { [SERVICE_NAME]: serviceName } }] }
- : { must_not: [{ exists: { field: SERVICE_NAME } }] };
+ const bool = serviceName
+ ? { filter: [{ term: { [SERVICE_NAME]: serviceName } }] }
+ : { must_not: [{ exists: { field: SERVICE_NAME } }] };
- const params = {
- index: indices.apmAgentConfigurationIndex,
- body: {
- size: 0,
- query: { bool },
- aggs: {
- environments: {
- terms: {
- field: SERVICE_ENVIRONMENT,
- missing: ALL_OPTION_VALUE,
- size: maxServiceEnvironments,
- },
+ const params = {
+ index: indices.apmAgentConfigurationIndex,
+ body: {
+ size: 0,
+ query: { bool },
+ aggs: {
+ environments: {
+ terms: {
+ field: SERVICE_ENVIRONMENT,
+ missing: ALL_OPTION_VALUE,
+ size: maxServiceEnvironments,
},
},
},
- };
+ },
+ };
- const resp = await internalClient.search(params);
- const existingEnvironments =
- resp.aggregations?.environments.buckets.map(
- (bucket) => bucket.key as string
- ) || [];
- return existingEnvironments;
- });
+ const resp = await internalClient.search(
+ 'get_existing_environments_for_service',
+ params
+ );
+ const existingEnvironments =
+ resp.aggregations?.environments.buckets.map(
+ (bucket) => bucket.key as string
+ ) || [];
+ return existingEnvironments;
}
diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts
index 9c56455f45902..0786bc6bc2771 100644
--- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts
+++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts
@@ -11,52 +11,52 @@ import { PromiseReturnType } from '../../../../../observability/typings/common';
import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames';
import { ALL_OPTION_VALUE } from '../../../../common/agent_configuration/all_option';
import { getProcessorEventForAggregatedTransactions } from '../../helpers/aggregated_transactions';
-import { withApmSpan } from '../../../utils/with_apm_span';
export type AgentConfigurationServicesAPIResponse = PromiseReturnType<
typeof getServiceNames
>;
-export function getServiceNames({
+export async function getServiceNames({
setup,
searchAggregatedTransactions,
}: {
setup: Setup;
searchAggregatedTransactions: boolean;
}) {
- return withApmSpan('get_service_names_for_agent_config', async () => {
- const { apmEventClient, config } = setup;
- const maxServiceSelection = config['xpack.apm.maxServiceSelection'];
+ const { apmEventClient, config } = setup;
+ const maxServiceSelection = config['xpack.apm.maxServiceSelection'];
- const params = {
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ProcessorEvent.error,
- ProcessorEvent.metric,
- ],
- },
- body: {
- timeout: '1ms',
- size: 0,
- aggs: {
- services: {
- terms: {
- field: SERVICE_NAME,
- size: maxServiceSelection,
- min_doc_count: 0,
- },
+ const params = {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ProcessorEvent.error,
+ ProcessorEvent.metric,
+ ],
+ },
+ body: {
+ timeout: '1ms',
+ size: 0,
+ aggs: {
+ services: {
+ terms: {
+ field: SERVICE_NAME,
+ size: maxServiceSelection,
+ min_doc_count: 0,
},
},
},
- };
+ },
+ };
- const resp = await apmEventClient.search(params);
- const serviceNames =
- resp.aggregations?.services.buckets
- .map((bucket) => bucket.key as string)
- .sort() || [];
- return [ALL_OPTION_VALUE, ...serviceNames];
- });
+ const resp = await apmEventClient.search(
+ 'get_service_names_for_agent_config',
+ params
+ );
+ const serviceNames =
+ resp.aggregations?.services.buckets
+ .map((bucket) => bucket.key as string)
+ .sort() || [];
+ return [ALL_OPTION_VALUE, ...serviceNames];
}
diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts
index adcfe88392dc8..098888c23ccbc 100644
--- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts
+++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts
@@ -8,7 +8,6 @@
import { Setup } from '../../helpers/setup_request';
import { AgentConfiguration } from '../../../../common/agent_configuration/configuration_types';
import { convertConfigSettingsToString } from './convert_settings_to_string';
-import { withApmSpan } from '../../../utils/with_apm_span';
export async function listConfigurations({ setup }: { setup: Setup }) {
const { internalClient, indices } = setup;
@@ -18,8 +17,9 @@ export async function listConfigurations({ setup }: { setup: Setup }) {
size: 200,
};
- const resp = await withApmSpan('list_agent_configurations', () =>
- internalClient.search(params)
+ const resp = await internalClient.search(
+ 'list_agent_configuration',
+ params
);
return resp.hits.hits
diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts
index 2026742a936a4..5fa4993921570 100644
--- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts
+++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts
@@ -29,5 +29,8 @@ export async function markAppliedByAgent({
},
};
- return internalClient.index(params);
+ return internalClient.index(
+ 'mark_configuration_applied_by_agent',
+ params
+ );
}
diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts
index 7454128a741d5..4e27953b3a315 100644
--- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts
+++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts
@@ -13,7 +13,6 @@ import {
import { Setup } from '../../helpers/setup_request';
import { AgentConfiguration } from '../../../../common/agent_configuration/configuration_types';
import { convertConfigSettingsToString } from './convert_settings_to_string';
-import { withApmSpan } from '../../../utils/with_apm_span';
export async function searchConfigurations({
service,
@@ -22,65 +21,64 @@ export async function searchConfigurations({
service: AgentConfiguration['service'];
setup: Setup;
}) {
- return withApmSpan('search_agent_configurations', async () => {
- const { internalClient, indices } = setup;
+ const { internalClient, indices } = setup;
- // In the following `constant_score` is being used to disable IDF calculation (where frequency of a term influences scoring).
- // Additionally a boost has been added to service.name to ensure it scores higher.
- // If there is tie between a config with a matching service.name and a config with a matching environment, the config that matches service.name wins
- const serviceNameFilter = service.name
- ? [
- {
- constant_score: {
- filter: { term: { [SERVICE_NAME]: service.name } },
- boost: 2,
- },
+ // In the following `constant_score` is being used to disable IDF calculation (where frequency of a term influences scoring).
+ // Additionally a boost has been added to service.name to ensure it scores higher.
+ // If there is tie between a config with a matching service.name and a config with a matching environment, the config that matches service.name wins
+ const serviceNameFilter = service.name
+ ? [
+ {
+ constant_score: {
+ filter: { term: { [SERVICE_NAME]: service.name } },
+ boost: 2,
},
- ]
- : [];
+ },
+ ]
+ : [];
- const environmentFilter = service.environment
- ? [
- {
- constant_score: {
- filter: { term: { [SERVICE_ENVIRONMENT]: service.environment } },
- boost: 1,
- },
+ const environmentFilter = service.environment
+ ? [
+ {
+ constant_score: {
+ filter: { term: { [SERVICE_ENVIRONMENT]: service.environment } },
+ boost: 1,
},
- ]
- : [];
+ },
+ ]
+ : [];
- const params = {
- index: indices.apmAgentConfigurationIndex,
- body: {
- query: {
- bool: {
- minimum_should_match: 2,
- should: [
- ...serviceNameFilter,
- ...environmentFilter,
- { bool: { must_not: [{ exists: { field: SERVICE_NAME } }] } },
- {
- bool: {
- must_not: [{ exists: { field: SERVICE_ENVIRONMENT } }],
- },
+ const params = {
+ index: indices.apmAgentConfigurationIndex,
+ body: {
+ query: {
+ bool: {
+ minimum_should_match: 2,
+ should: [
+ ...serviceNameFilter,
+ ...environmentFilter,
+ { bool: { must_not: [{ exists: { field: SERVICE_NAME } }] } },
+ {
+ bool: {
+ must_not: [{ exists: { field: SERVICE_ENVIRONMENT } }],
},
- ],
- },
+ },
+ ],
},
},
- };
+ },
+ };
- const resp = await internalClient.search(
- params
- );
+ const resp = await internalClient.search(
+ 'search_agent_configurations',
+ params
+ );
- const hit = resp.hits.hits[0] as SearchHit | undefined;
+ const hit = resp.hits.hits[0] as SearchHit | undefined;
- if (!hit) {
- return;
- }
+ if (!hit) {
+ return;
+ }
- return convertConfigSettingsToString(hit);
- });
+ return convertConfigSettingsToString(hit);
}
diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.test.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.test.ts
index 47ee91232ea48..051b9e2809e49 100644
--- a/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.test.ts
+++ b/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.test.ts
@@ -39,17 +39,20 @@ describe('Create or Update Custom link', () => {
it('creates a new custom link', () => {
createOrUpdateCustomLink({ customLink, setup: mockedSetup });
- expect(internalClientIndexMock).toHaveBeenCalledWith({
- refresh: true,
- index: 'apmCustomLinkIndex',
- body: {
- '@timestamp': 1570737000000,
- label: 'foo',
- url: 'http://elastic.com/{{trace.id}}',
- 'service.name': ['opbeans-java'],
- 'transaction.type': ['Request'],
- },
- });
+ expect(internalClientIndexMock).toHaveBeenCalledWith(
+ 'create_or_update_custom_link',
+ {
+ refresh: true,
+ index: 'apmCustomLinkIndex',
+ body: {
+ '@timestamp': 1570737000000,
+ label: 'foo',
+ url: 'http://elastic.com/{{trace.id}}',
+ 'service.name': ['opbeans-java'],
+ 'transaction.type': ['Request'],
+ },
+ }
+ );
});
it('update a new custom link', () => {
createOrUpdateCustomLink({
@@ -57,17 +60,20 @@ describe('Create or Update Custom link', () => {
customLink,
setup: mockedSetup,
});
- expect(internalClientIndexMock).toHaveBeenCalledWith({
- refresh: true,
- index: 'apmCustomLinkIndex',
- id: 'bar',
- body: {
- '@timestamp': 1570737000000,
- label: 'foo',
- url: 'http://elastic.com/{{trace.id}}',
- 'service.name': ['opbeans-java'],
- 'transaction.type': ['Request'],
- },
- });
+ expect(internalClientIndexMock).toHaveBeenCalledWith(
+ 'create_or_update_custom_link',
+ {
+ refresh: true,
+ index: 'apmCustomLinkIndex',
+ id: 'bar',
+ body: {
+ '@timestamp': 1570737000000,
+ label: 'foo',
+ url: 'http://elastic.com/{{trace.id}}',
+ 'service.name': ['opbeans-java'],
+ 'transaction.type': ['Request'],
+ },
+ }
+ );
});
});
diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts
index 7e546fb555036..8f14e87fe183b 100644
--- a/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts
+++ b/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts
@@ -12,7 +12,6 @@ import {
import { Setup } from '../../helpers/setup_request';
import { toESFormat } from './helper';
import { APMIndexDocumentParams } from '../../helpers/create_es_client/create_internal_es_client';
-import { withApmSpan } from '../../../utils/with_apm_span';
export function createOrUpdateCustomLink({
customLinkId,
@@ -23,23 +22,21 @@ export function createOrUpdateCustomLink({
customLink: Omit;
setup: Setup;
}) {
- return withApmSpan('create_or_update_custom_link', () => {
- const { internalClient, indices } = setup;
+ const { internalClient, indices } = setup;
- const params: APMIndexDocumentParams = {
- refresh: true,
- index: indices.apmCustomLinkIndex,
- body: {
- '@timestamp': Date.now(),
- ...toESFormat(customLink),
- },
- };
+ const params: APMIndexDocumentParams = {
+ refresh: true,
+ index: indices.apmCustomLinkIndex,
+ body: {
+ '@timestamp': Date.now(),
+ ...toESFormat(customLink),
+ },
+ };
- // by specifying an id elasticsearch will delete the previous doc and insert the updated doc
- if (customLinkId) {
- params.id = customLinkId;
- }
+ // by specifying an id elasticsearch will delete the previous doc and insert the updated doc
+ if (customLinkId) {
+ params.id = customLinkId;
+ }
- return internalClient.index(params);
- });
+ return internalClient.index('create_or_update_custom_link', params);
}
diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/delete_custom_link.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/delete_custom_link.ts
index 7c88bcc43cc7f..bf7cfb33d87ac 100644
--- a/x-pack/plugins/apm/server/lib/settings/custom_link/delete_custom_link.ts
+++ b/x-pack/plugins/apm/server/lib/settings/custom_link/delete_custom_link.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-import { withApmSpan } from '../../../utils/with_apm_span';
import { Setup } from '../../helpers/setup_request';
export function deleteCustomLink({
@@ -15,15 +14,13 @@ export function deleteCustomLink({
customLinkId: string;
setup: Setup;
}) {
- return withApmSpan('delete_custom_link', () => {
- const { internalClient, indices } = setup;
+ const { internalClient, indices } = setup;
- const params = {
- refresh: 'wait_for' as const,
- index: indices.apmCustomLinkIndex,
- id: customLinkId,
- };
+ const params = {
+ refresh: 'wait_for' as const,
+ index: indices.apmCustomLinkIndex,
+ id: customLinkId,
+ };
- return internalClient.delete(params);
- });
+ return internalClient.delete('delete_custom_link', params);
}
diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.ts
index d3d9b45285354..91bc8c85bc014 100644
--- a/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.ts
+++ b/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.ts
@@ -11,43 +11,43 @@ import { Setup } from '../../helpers/setup_request';
import { ProcessorEvent } from '../../../../common/processor_event';
import { filterOptionsRt } from './custom_link_types';
import { splitFilterValueByComma } from './helper';
-import { withApmSpan } from '../../../utils/with_apm_span';
-export function getTransaction({
+export async function getTransaction({
setup,
filters = {},
}: {
setup: Setup;
filters?: t.TypeOf;
}) {
- return withApmSpan('get_transaction_for_custom_link', async () => {
- const { apmEventClient } = setup;
+ const { apmEventClient } = setup;
- const esFilters = compact(
- Object.entries(filters)
- // loops through the filters splitting the value by comma and removing white spaces
- .map(([key, value]) => {
- if (value) {
- return { terms: { [key]: splitFilterValueByComma(value) } };
- }
- })
- );
+ const esFilters = compact(
+ Object.entries(filters)
+ // loops through the filters splitting the value by comma and removing white spaces
+ .map(([key, value]) => {
+ if (value) {
+ return { terms: { [key]: splitFilterValueByComma(value) } };
+ }
+ })
+ );
- const params = {
- terminateAfter: 1,
- apm: {
- events: [ProcessorEvent.transaction as const],
- },
- size: 1,
- body: {
- query: {
- bool: {
- filter: esFilters,
- },
+ const params = {
+ terminateAfter: 1,
+ apm: {
+ events: [ProcessorEvent.transaction as const],
+ },
+ size: 1,
+ body: {
+ query: {
+ bool: {
+ filter: esFilters,
},
},
- };
- const resp = await apmEventClient.search(params);
- return resp.hits.hits[0]?._source;
- });
+ },
+ };
+ const resp = await apmEventClient.search(
+ 'get_transaction_for_custom_link',
+ params
+ );
+ return resp.hits.hits[0]?._source;
}
diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts
index 0eac2e08d0901..d477da85e0d9b 100644
--- a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts
+++ b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts
@@ -14,54 +14,54 @@ import {
import { Setup } from '../../helpers/setup_request';
import { fromESFormat } from './helper';
import { filterOptionsRt } from './custom_link_types';
-import { withApmSpan } from '../../../utils/with_apm_span';
-export function listCustomLinks({
+export async function listCustomLinks({
setup,
filters = {},
}: {
setup: Setup;
filters?: t.TypeOf;
}): Promise {
- return withApmSpan('list_custom_links', async () => {
- const { internalClient, indices } = setup;
- const esFilters = Object.entries(filters).map(([key, value]) => {
- return {
+ const { internalClient, indices } = setup;
+ const esFilters = Object.entries(filters).map(([key, value]) => {
+ return {
+ bool: {
+ minimum_should_match: 1,
+ should: [
+ { term: { [key]: value } },
+ { bool: { must_not: [{ exists: { field: key } }] } },
+ ] as QueryDslQueryContainer[],
+ },
+ };
+ });
+
+ const params = {
+ index: indices.apmCustomLinkIndex,
+ size: 500,
+ body: {
+ query: {
bool: {
- minimum_should_match: 1,
- should: [
- { term: { [key]: value } },
- { bool: { must_not: [{ exists: { field: key } }] } },
- ] as QueryDslQueryContainer[],
+ filter: esFilters,
},
- };
- });
-
- const params = {
- index: indices.apmCustomLinkIndex,
- size: 500,
- body: {
- query: {
- bool: {
- filter: esFilters,
+ },
+ sort: [
+ {
+ 'label.keyword': {
+ order: 'asc' as const,
},
},
- sort: [
- {
- 'label.keyword': {
- order: 'asc' as const,
- },
- },
- ],
- },
- };
- const resp = await internalClient.search(params);
- const customLinks = resp.hits.hits.map((item) =>
- fromESFormat({
- id: item._id,
- ...item._source,
- })
- );
- return customLinks;
- });
+ ],
+ },
+ };
+ const resp = await internalClient.search(
+ 'list_custom_links',
+ params
+ );
+ const customLinks = resp.hits.hits.map((item) =>
+ fromESFormat({
+ id: item._id,
+ ...item._source,
+ })
+ );
+ return customLinks;
}
diff --git a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts
index 157f09978eaec..68d316ef55df9 100644
--- a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts
+++ b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts
@@ -19,7 +19,6 @@ import { APMError } from '../../../typings/es_schemas/ui/apm_error';
import { rangeQuery } from '../../../server/utils/queries';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
import { PromiseValueType } from '../../../typings/common';
-import { withApmSpan } from '../../utils/with_apm_span';
export interface ErrorsPerTransaction {
[transactionId: string]: number;
@@ -29,103 +28,94 @@ export async function getTraceItems(
traceId: string,
setup: Setup & SetupTimeRange
) {
- return withApmSpan('get_trace_items', async () => {
- const { start, end, apmEventClient, config } = setup;
- const maxTraceItems = config['xpack.apm.ui.maxTraceItems'];
- const excludedLogLevels = ['debug', 'info', 'warning'];
+ const { start, end, apmEventClient, config } = setup;
+ const maxTraceItems = config['xpack.apm.ui.maxTraceItems'];
+ const excludedLogLevels = ['debug', 'info', 'warning'];
- const errorResponsePromise = withApmSpan('get_trace_error_items', () =>
- apmEventClient.search({
- apm: {
- events: [ProcessorEvent.error],
+ const errorResponsePromise = apmEventClient.search('get_trace_items', {
+ apm: {
+ events: [ProcessorEvent.error],
+ },
+ body: {
+ size: maxTraceItems,
+ query: {
+ bool: {
+ filter: [
+ { term: { [TRACE_ID]: traceId } },
+ ...rangeQuery(start, end),
+ ],
+ must_not: { terms: { [ERROR_LOG_LEVEL]: excludedLogLevels } },
},
- body: {
- size: maxTraceItems,
- query: {
- bool: {
- filter: [
- { term: { [TRACE_ID]: traceId } },
- ...rangeQuery(start, end),
- ],
- must_not: { terms: { [ERROR_LOG_LEVEL]: excludedLogLevels } },
- },
- },
- aggs: {
- by_transaction_id: {
- terms: {
- field: TRANSACTION_ID,
- size: maxTraceItems,
- // high cardinality
- execution_hint: 'map' as const,
- },
- },
+ },
+ aggs: {
+ by_transaction_id: {
+ terms: {
+ field: TRANSACTION_ID,
+ size: maxTraceItems,
+ // high cardinality
+ execution_hint: 'map' as const,
},
},
- })
- );
+ },
+ },
+ });
- const traceResponsePromise = withApmSpan('get_trace_span_items', () =>
- apmEventClient.search({
- apm: {
- events: [ProcessorEvent.span, ProcessorEvent.transaction],
- },
- body: {
- size: maxTraceItems,
- query: {
- bool: {
- filter: [
- { term: { [TRACE_ID]: traceId } },
- ...rangeQuery(start, end),
- ] as QueryDslQueryContainer[],
- should: {
- exists: { field: PARENT_ID },
- },
- },
+ const traceResponsePromise = apmEventClient.search('get_trace_span_items', {
+ apm: {
+ events: [ProcessorEvent.span, ProcessorEvent.transaction],
+ },
+ body: {
+ size: maxTraceItems,
+ query: {
+ bool: {
+ filter: [
+ { term: { [TRACE_ID]: traceId } },
+ ...rangeQuery(start, end),
+ ] as QueryDslQueryContainer[],
+ should: {
+ exists: { field: PARENT_ID },
},
- sort: [
- { _score: { order: 'asc' as const } },
- { [TRANSACTION_DURATION]: { order: 'desc' as const } },
- { [SPAN_DURATION]: { order: 'desc' as const } },
- ],
- track_total_hits: true,
},
- })
- );
+ },
+ sort: [
+ { _score: { order: 'asc' as const } },
+ { [TRANSACTION_DURATION]: { order: 'desc' as const } },
+ { [SPAN_DURATION]: { order: 'desc' as const } },
+ ],
+ track_total_hits: true,
+ },
+ });
- const [errorResponse, traceResponse]: [
- // explicit intermediary types to avoid TS "excessively deep" error
- PromiseValueType,
- PromiseValueType
- ] = (await Promise.all([
- errorResponsePromise,
- traceResponsePromise,
- ])) as any;
+ const [errorResponse, traceResponse]: [
+ // explicit intermediary types to avoid TS "excessively deep" error
+ PromiseValueType,
+ PromiseValueType
+ ] = (await Promise.all([errorResponsePromise, traceResponsePromise])) as any;
- const exceedsMax = traceResponse.hits.total.value > maxTraceItems;
+ const exceedsMax = traceResponse.hits.total.value > maxTraceItems;
- const items = traceResponse.hits.hits.map((hit) => hit._source);
+ const items = traceResponse.hits.hits.map((hit) => hit._source);
- const errorFrequencies: {
- errorsPerTransaction: ErrorsPerTransaction;
- errorDocs: APMError[];
- } = {
- errorDocs: errorResponse.hits.hits.map(({ _source }) => _source),
- errorsPerTransaction:
- errorResponse.aggregations?.by_transaction_id.buckets.reduce(
- (acc, current) => {
- return {
- ...acc,
- [current.key]: current.doc_count,
- };
- },
- {} as ErrorsPerTransaction
- ) ?? {},
- };
+ const errorFrequencies: {
+ errorsPerTransaction: ErrorsPerTransaction;
+ errorDocs: APMError[];
+ } = {
+ errorDocs: errorResponse.hits.hits.map(({ _source }) => _source),
+ errorsPerTransaction:
+ errorResponse.aggregations?.by_transaction_id.buckets.reduce(
+ (acc, current) => {
+ return {
+ ...acc,
+ [current.key]: current.doc_count,
+ };
+ },
+ {} as ErrorsPerTransaction
+ ) ?? {},
+ };
- return {
- items,
- exceedsMax,
- ...errorFrequencies,
- };
- });
+ return {
+ items,
+ exceedsMax,
+ ...errorFrequencies,
+ };
}
diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts
index 71f803a03bf85..6499e80be9302 100644
--- a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts
+++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts
@@ -31,7 +31,6 @@ import {
getOutcomeAggregation,
getTransactionErrorRateTimeSeries,
} from '../helpers/transaction_error_rate';
-import { withApmSpan } from '../../utils/with_apm_span';
export async function getErrorRate({
environment,
@@ -58,81 +57,82 @@ export async function getErrorRate({
transactionErrorRate: Coordinate[];
average: number | null;
}> {
- return withApmSpan('get_transaction_group_error_rate', async () => {
- const { apmEventClient } = setup;
-
- const transactionNamefilter = transactionName
- ? [{ term: { [TRANSACTION_NAME]: transactionName } }]
- : [];
- const transactionTypefilter = transactionType
- ? [{ term: { [TRANSACTION_TYPE]: transactionType } }]
- : [];
-
- const filter = [
- { term: { [SERVICE_NAME]: serviceName } },
- {
- terms: {
- [EVENT_OUTCOME]: [EventOutcome.failure, EventOutcome.success],
- },
- },
- ...transactionNamefilter,
- ...transactionTypefilter,
- ...getDocumentTypeFilterForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ];
-
- const outcomes = getOutcomeAggregation();
-
- const params = {
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ],
+ const { apmEventClient } = setup;
+
+ const transactionNamefilter = transactionName
+ ? [{ term: { [TRANSACTION_NAME]: transactionName } }]
+ : [];
+ const transactionTypefilter = transactionType
+ ? [{ term: { [TRANSACTION_TYPE]: transactionType } }]
+ : [];
+
+ const filter = [
+ { term: { [SERVICE_NAME]: serviceName } },
+ {
+ terms: {
+ [EVENT_OUTCOME]: [EventOutcome.failure, EventOutcome.success],
},
- body: {
- size: 0,
- query: { bool: { filter } },
- aggs: {
- outcomes,
- timeseries: {
- date_histogram: {
- field: '@timestamp',
- fixed_interval: getBucketSize({ start, end }).intervalString,
- min_doc_count: 0,
- extended_bounds: { min: start, max: end },
- },
- aggs: {
- outcomes,
- },
+ },
+ ...transactionNamefilter,
+ ...transactionTypefilter,
+ ...getDocumentTypeFilterForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ];
+
+ const outcomes = getOutcomeAggregation();
+
+ const params = {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ],
+ },
+ body: {
+ size: 0,
+ query: { bool: { filter } },
+ aggs: {
+ outcomes,
+ timeseries: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: getBucketSize({ start, end }).intervalString,
+ min_doc_count: 0,
+ extended_bounds: { min: start, max: end },
+ },
+ aggs: {
+ outcomes,
},
},
},
- };
+ },
+ };
- const resp = await apmEventClient.search(params);
+ const resp = await apmEventClient.search(
+ 'get_transaction_group_error_rate',
+ params
+ );
- const noHits = resp.hits.total.value === 0;
+ const noHits = resp.hits.total.value === 0;
- if (!resp.aggregations) {
- return { noHits, transactionErrorRate: [], average: null };
- }
+ if (!resp.aggregations) {
+ return { noHits, transactionErrorRate: [], average: null };
+ }
- const transactionErrorRate = getTransactionErrorRateTimeSeries(
- resp.aggregations.timeseries.buckets
- );
+ const transactionErrorRate = getTransactionErrorRateTimeSeries(
+ resp.aggregations.timeseries.buckets
+ );
- const average = calculateTransactionErrorPercentage(
- resp.aggregations.outcomes
- );
+ const average = calculateTransactionErrorPercentage(
+ resp.aggregations.outcomes
+ );
- return { noHits, transactionErrorRate, average };
- });
+ return { noHits, transactionErrorRate, average };
}
export async function getErrorRatePeriods({
diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts
index 8156d52d984df..34fd86f2fc598 100644
--- a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts
+++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts
@@ -11,7 +11,6 @@ import { TRANSACTION_TYPE } from '../../../common/elasticsearch_fieldnames';
import { arrayUnionToCallable } from '../../../common/utils/array_union_to_callable';
import { TransactionGroupRequestBase, TransactionGroupSetup } from './fetcher';
import { getTransactionDurationFieldForAggregatedTransactions } from '../helpers/aggregated_transactions';
-import { withApmSpan } from '../../utils/with_apm_span';
interface MetricParams {
request: TransactionGroupRequestBase;
@@ -39,124 +38,128 @@ function mergeRequestWithAggs<
});
}
-export function getAverages({
+export async function getAverages({
request,
setup,
searchAggregatedTransactions,
}: MetricParams) {
- return withApmSpan('get_avg_transaction_group_duration', async () => {
- const params = mergeRequestWithAggs(request, {
+ const params = mergeRequestWithAggs(request, {
+ avg: {
avg: {
- avg: {
- field: getTransactionDurationFieldForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- },
+ field: getTransactionDurationFieldForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
},
- });
-
- const response = await setup.apmEventClient.search(params);
-
- return arrayUnionToCallable(
- response.aggregations?.transaction_groups.buckets ?? []
- ).map((bucket) => {
- return {
- key: bucket.key as BucketKey,
- avg: bucket.avg.value,
- };
- });
+ },
+ });
+
+ const response = await setup.apmEventClient.search(
+ 'get_avg_transaction_group_duration',
+ params
+ );
+
+ return arrayUnionToCallable(
+ response.aggregations?.transaction_groups.buckets ?? []
+ ).map((bucket) => {
+ return {
+ key: bucket.key as BucketKey,
+ avg: bucket.avg.value,
+ };
});
}
-export function getCounts({ request, setup }: MetricParams) {
- return withApmSpan('get_transaction_group_transaction_count', async () => {
- const params = mergeRequestWithAggs(request, {
- transaction_type: {
- top_metrics: {
- sort: {
- '@timestamp': 'desc' as const,
- },
- metrics: [
- {
- field: TRANSACTION_TYPE,
- } as const,
- ],
+export async function getCounts({ request, setup }: MetricParams) {
+ const params = mergeRequestWithAggs(request, {
+ transaction_type: {
+ top_metrics: {
+ sort: {
+ '@timestamp': 'desc' as const,
},
+ metrics: [
+ {
+ field: TRANSACTION_TYPE,
+ } as const,
+ ],
},
- });
-
- const response = await setup.apmEventClient.search(params);
-
- return arrayUnionToCallable(
- response.aggregations?.transaction_groups.buckets ?? []
- ).map((bucket) => {
- return {
- key: bucket.key as BucketKey,
- count: bucket.doc_count,
- transactionType: bucket.transaction_type.top[0].metrics[
- TRANSACTION_TYPE
- ] as string,
- };
- });
+ },
+ });
+
+ const response = await setup.apmEventClient.search(
+ 'get_transaction_group_transaction_count',
+ params
+ );
+
+ return arrayUnionToCallable(
+ response.aggregations?.transaction_groups.buckets ?? []
+ ).map((bucket) => {
+ return {
+ key: bucket.key as BucketKey,
+ count: bucket.doc_count,
+ transactionType: bucket.transaction_type.top[0].metrics[
+ TRANSACTION_TYPE
+ ] as string,
+ };
});
}
-export function getSums({
+export async function getSums({
request,
setup,
searchAggregatedTransactions,
}: MetricParams) {
- return withApmSpan('get_transaction_group_latency_sums', async () => {
- const params = mergeRequestWithAggs(request, {
+ const params = mergeRequestWithAggs(request, {
+ sum: {
sum: {
- sum: {
- field: getTransactionDurationFieldForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- },
+ field: getTransactionDurationFieldForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
},
- });
-
- const response = await setup.apmEventClient.search(params);
-
- return arrayUnionToCallable(
- response.aggregations?.transaction_groups.buckets ?? []
- ).map((bucket) => {
- return {
- key: bucket.key as BucketKey,
- sum: bucket.sum.value,
- };
- });
+ },
+ });
+
+ const response = await setup.apmEventClient.search(
+ 'get_transaction_group_latency_sums',
+ params
+ );
+
+ return arrayUnionToCallable(
+ response.aggregations?.transaction_groups.buckets ?? []
+ ).map((bucket) => {
+ return {
+ key: bucket.key as BucketKey,
+ sum: bucket.sum.value,
+ };
});
}
-export function getPercentiles({
+export async function getPercentiles({
request,
setup,
searchAggregatedTransactions,
}: MetricParams) {
- return withApmSpan('get_transaction_group_latency_percentiles', async () => {
- const params = mergeRequestWithAggs(request, {
- p95: {
- percentiles: {
- field: getTransactionDurationFieldForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- hdr: { number_of_significant_value_digits: 2 },
- percents: [95],
- },
+ const params = mergeRequestWithAggs(request, {
+ p95: {
+ percentiles: {
+ field: getTransactionDurationFieldForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ hdr: { number_of_significant_value_digits: 2 },
+ percents: [95],
},
- });
-
- const response = await setup.apmEventClient.search(params);
-
- return arrayUnionToCallable(
- response.aggregations?.transaction_groups.buckets ?? []
- ).map((bucket) => {
- return {
- key: bucket.key as BucketKey,
- p95: Object.values(bucket.p95.values)[0],
- };
- });
+ },
+ });
+
+ const response = await setup.apmEventClient.search(
+ 'get_transaction_group_latency_percentiles',
+ params
+ );
+
+ return arrayUnionToCallable(
+ response.aggregations?.transaction_groups.buckets ?? []
+ ).map((bucket) => {
+ return {
+ key: bucket.key as BucketKey,
+ p95: Object.values(bucket.p95.values)[0],
+ };
});
}
diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/queries.test.ts b/x-pack/plugins/apm/server/lib/transaction_groups/queries.test.ts
index 6cb85b62205b8..5c1754cd36ef4 100644
--- a/x-pack/plugins/apm/server/lib/transaction_groups/queries.test.ts
+++ b/x-pack/plugins/apm/server/lib/transaction_groups/queries.test.ts
@@ -33,7 +33,7 @@ describe('transaction group queries', () => {
)
);
- const allParams = mock.spy.mock.calls.map((call) => call[0]);
+ const allParams = mock.spy.mock.calls.map((call) => call[1]);
expect(allParams).toMatchSnapshot();
});
@@ -51,7 +51,7 @@ describe('transaction group queries', () => {
)
);
- const allParams = mock.spy.mock.calls.map((call) => call[0]);
+ const allParams = mock.spy.mock.calls.map((call) => call[1]);
expect(allParams).toMatchSnapshot();
});
diff --git a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts
index 568769b52e2b4..20534a5fa7cbf 100644
--- a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts
@@ -26,9 +26,8 @@ import {
import { getMetricsDateHistogramParams } from '../../helpers/metrics';
import { MAX_KPIS } from './constants';
import { getVizColorForIndex } from '../../../../common/viz_colors';
-import { withApmSpan } from '../../../utils/with_apm_span';
-export function getTransactionBreakdown({
+export async function getTransactionBreakdown({
environment,
kuery,
setup,
@@ -43,205 +42,203 @@ export function getTransactionBreakdown({
transactionName?: string;
transactionType: string;
}) {
- return withApmSpan('get_transaction_breakdown', async () => {
- const { apmEventClient, start, end, config } = setup;
+ const { apmEventClient, start, end, config } = setup;
- const subAggs = {
- sum_all_self_times: {
- sum: {
- field: SPAN_SELF_TIME_SUM,
- },
+ const subAggs = {
+ sum_all_self_times: {
+ sum: {
+ field: SPAN_SELF_TIME_SUM,
},
- total_transaction_breakdown_count: {
- sum: {
- field: TRANSACTION_BREAKDOWN_COUNT,
- },
+ },
+ total_transaction_breakdown_count: {
+ sum: {
+ field: TRANSACTION_BREAKDOWN_COUNT,
},
- types: {
- terms: {
- field: SPAN_TYPE,
- size: 20,
- order: {
- _count: 'desc' as const,
- },
+ },
+ types: {
+ terms: {
+ field: SPAN_TYPE,
+ size: 20,
+ order: {
+ _count: 'desc' as const,
},
- aggs: {
- subtypes: {
- terms: {
- field: SPAN_SUBTYPE,
- missing: '',
- size: 20,
- order: {
- _count: 'desc' as const,
- },
+ },
+ aggs: {
+ subtypes: {
+ terms: {
+ field: SPAN_SUBTYPE,
+ missing: '',
+ size: 20,
+ order: {
+ _count: 'desc' as const,
},
- aggs: {
- total_self_time_per_subtype: {
- sum: {
- field: SPAN_SELF_TIME_SUM,
- },
+ },
+ aggs: {
+ total_self_time_per_subtype: {
+ sum: {
+ field: SPAN_SELF_TIME_SUM,
},
},
},
},
},
- };
-
- const filters = [
- { term: { [SERVICE_NAME]: serviceName } },
- { term: { [TRANSACTION_TYPE]: transactionType } },
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- {
+ },
+ };
+
+ const filters = [
+ { term: { [SERVICE_NAME]: serviceName } },
+ { term: { [TRANSACTION_TYPE]: transactionType } },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ {
+ bool: {
+ should: [
+ { exists: { field: SPAN_SELF_TIME_SUM } },
+ { exists: { field: TRANSACTION_BREAKDOWN_COUNT } },
+ ],
+ minimum_should_match: 1,
+ },
+ },
+ ];
+
+ if (transactionName) {
+ filters.push({ term: { [TRANSACTION_NAME]: transactionName } });
+ }
+
+ const params = {
+ apm: {
+ events: [ProcessorEvent.metric],
+ },
+ body: {
+ size: 0,
+ query: {
bool: {
- should: [
- { exists: { field: SPAN_SELF_TIME_SUM } },
- { exists: { field: TRANSACTION_BREAKDOWN_COUNT } },
- ],
- minimum_should_match: 1,
+ filter: filters,
},
},
- ];
-
- if (transactionName) {
- filters.push({ term: { [TRANSACTION_NAME]: transactionName } });
- }
-
- const params = {
- apm: {
- events: [ProcessorEvent.metric],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter: filters,
- },
- },
- aggs: {
- ...subAggs,
- by_date: {
- date_histogram: getMetricsDateHistogramParams(
- start,
- end,
- config['xpack.apm.metricsInterval']
- ),
- aggs: subAggs,
- },
+ aggs: {
+ ...subAggs,
+ by_date: {
+ date_histogram: getMetricsDateHistogramParams(
+ start,
+ end,
+ config['xpack.apm.metricsInterval']
+ ),
+ aggs: subAggs,
},
},
+ },
+ };
+
+ const resp = await apmEventClient.search('get_transaction_breakdown', params);
+
+ const formatBucket = (
+ aggs:
+ | Required['aggregations']
+ | Required['aggregations']['by_date']['buckets'][0]
+ ) => {
+ const sumAllSelfTimes = aggs.sum_all_self_times.value || 0;
+
+ const breakdowns = flatten(
+ aggs.types.buckets.map((bucket) => {
+ const type = bucket.key as string;
+
+ return bucket.subtypes.buckets.map((subBucket) => {
+ return {
+ name: (subBucket.key as string) || type,
+ percentage:
+ (subBucket.total_self_time_per_subtype.value || 0) /
+ sumAllSelfTimes,
+ };
+ });
+ })
+ );
+
+ return breakdowns;
+ };
+
+ const visibleKpis = resp.aggregations
+ ? orderBy(formatBucket(resp.aggregations), 'percentage', 'desc').slice(
+ 0,
+ MAX_KPIS
+ )
+ : [];
+
+ const kpis = orderBy(
+ visibleKpis.map((kpi) => ({
+ ...kpi,
+ lowerCaseName: kpi.name.toLowerCase(),
+ })),
+ 'lowerCaseName'
+ ).map((kpi, index) => {
+ const { lowerCaseName, ...rest } = kpi;
+ return {
+ ...rest,
+ color: getVizColorForIndex(index),
};
+ });
- const resp = await apmEventClient.search(params);
-
- const formatBucket = (
- aggs:
- | Required['aggregations']
- | Required['aggregations']['by_date']['buckets'][0]
- ) => {
- const sumAllSelfTimes = aggs.sum_all_self_times.value || 0;
-
- const breakdowns = flatten(
- aggs.types.buckets.map((bucket) => {
- const type = bucket.key as string;
-
- return bucket.subtypes.buckets.map((subBucket) => {
- return {
- name: (subBucket.key as string) || type,
- percentage:
- (subBucket.total_self_time_per_subtype.value || 0) /
- sumAllSelfTimes,
- };
- });
- })
- );
-
- return breakdowns;
- };
-
- const visibleKpis = resp.aggregations
- ? orderBy(formatBucket(resp.aggregations), 'percentage', 'desc').slice(
- 0,
- MAX_KPIS
- )
- : [];
-
- const kpis = orderBy(
- visibleKpis.map((kpi) => ({
- ...kpi,
- lowerCaseName: kpi.name.toLowerCase(),
- })),
- 'lowerCaseName'
- ).map((kpi, index) => {
- const { lowerCaseName, ...rest } = kpi;
- return {
- ...rest,
- color: getVizColorForIndex(index),
- };
- });
-
- const kpiNames = kpis.map((kpi) => kpi.name);
+ const kpiNames = kpis.map((kpi) => kpi.name);
- const bucketsByDate = resp.aggregations?.by_date.buckets || [];
+ const bucketsByDate = resp.aggregations?.by_date.buckets || [];
- const timeseriesPerSubtype = bucketsByDate.reduce((prev, bucket) => {
- const formattedValues = formatBucket(bucket);
- const time = bucket.key;
+ const timeseriesPerSubtype = bucketsByDate.reduce((prev, bucket) => {
+ const formattedValues = formatBucket(bucket);
+ const time = bucket.key;
- const updatedSeries = kpiNames.reduce((p, kpiName) => {
- const { name, percentage } = formattedValues.find(
- (val) => val.name === kpiName
- ) || {
- name: kpiName,
- percentage: null,
- };
+ const updatedSeries = kpiNames.reduce((p, kpiName) => {
+ const { name, percentage } = formattedValues.find(
+ (val) => val.name === kpiName
+ ) || {
+ name: kpiName,
+ percentage: null,
+ };
- if (!p[name]) {
- p[name] = [];
- }
- return {
- ...p,
- [name]: p[name].concat({
- x: time,
- y: percentage,
- }),
- };
- }, prev);
-
- const lastValues = Object.values(updatedSeries).map(last);
-
- // If for a given timestamp, some series have data, but others do not,
- // we have to set any null values to 0 to make sure the stacked area chart
- // is drawn correctly.
- // If we set all values to 0, the chart always displays null values as 0,
- // and the chart looks weird.
- const hasAnyValues = lastValues.some((value) => value?.y !== null);
- const hasNullValues = lastValues.some((value) => value?.y === null);
-
- if (hasAnyValues && hasNullValues) {
- Object.values(updatedSeries).forEach((series) => {
- const value = series[series.length - 1];
- const isEmpty = value.y === null;
- if (isEmpty) {
- // local mutation to prevent complicated map/reduce calls
- value.y = 0;
- }
- });
+ if (!p[name]) {
+ p[name] = [];
}
+ return {
+ ...p,
+ [name]: p[name].concat({
+ x: time,
+ y: percentage,
+ }),
+ };
+ }, prev);
+
+ const lastValues = Object.values(updatedSeries).map(last);
+
+ // If for a given timestamp, some series have data, but others do not,
+ // we have to set any null values to 0 to make sure the stacked area chart
+ // is drawn correctly.
+ // If we set all values to 0, the chart always displays null values as 0,
+ // and the chart looks weird.
+ const hasAnyValues = lastValues.some((value) => value?.y !== null);
+ const hasNullValues = lastValues.some((value) => value?.y === null);
+
+ if (hasAnyValues && hasNullValues) {
+ Object.values(updatedSeries).forEach((series) => {
+ const value = series[series.length - 1];
+ const isEmpty = value.y === null;
+ if (isEmpty) {
+ // local mutation to prevent complicated map/reduce calls
+ value.y = 0;
+ }
+ });
+ }
- return updatedSeries;
- }, {} as Record>);
+ return updatedSeries;
+ }, {} as Record>);
- const timeseries = kpis.map((kpi) => ({
- title: kpi.name,
- color: kpi.color,
- type: 'areaStacked',
- data: timeseriesPerSubtype[kpi.name],
- hideLegend: false,
- legendValue: asPercent(kpi.percentage, 1),
- }));
+ const timeseries = kpis.map((kpi) => ({
+ title: kpi.name,
+ color: kpi.color,
+ type: 'areaStacked',
+ data: timeseriesPerSubtype[kpi.name],
+ hideLegend: false,
+ legendValue: asPercent(kpi.percentage, 1),
+ }));
- return { timeseries };
- });
+ return { timeseries };
}
diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts
index 3b4319c37996d..6259bb75386fb 100644
--- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts
@@ -89,48 +89,47 @@ export async function getBuckets({
] as QueryDslQueryContainer[];
async function getSamplesForDistributionBuckets() {
- const response = await withApmSpan(
+ const response = await apmEventClient.search(
'get_samples_for_latency_distribution_buckets',
- () =>
- apmEventClient.search({
- apm: {
- events: [ProcessorEvent.transaction],
- },
- body: {
- query: {
- bool: {
- filter: [
- ...commonFilters,
- { term: { [TRANSACTION_SAMPLED]: true } },
- ],
- should: [
- { term: { [TRACE_ID]: traceId } },
- { term: { [TRANSACTION_ID]: transactionId } },
- ] as QueryDslQueryContainer[],
- },
+ {
+ apm: {
+ events: [ProcessorEvent.transaction],
+ },
+ body: {
+ query: {
+ bool: {
+ filter: [
+ ...commonFilters,
+ { term: { [TRANSACTION_SAMPLED]: true } },
+ ],
+ should: [
+ { term: { [TRACE_ID]: traceId } },
+ { term: { [TRANSACTION_ID]: transactionId } },
+ ] as QueryDslQueryContainer[],
},
- aggs: {
- distribution: {
- histogram: getHistogramAggOptions({
- bucketSize,
- field: TRANSACTION_DURATION,
- distributionMax,
- }),
- aggs: {
- samples: {
- top_hits: {
- _source: [TRANSACTION_ID, TRACE_ID],
- size: 10,
- sort: {
- _score: 'desc' as const,
- },
+ },
+ aggs: {
+ distribution: {
+ histogram: getHistogramAggOptions({
+ bucketSize,
+ field: TRANSACTION_DURATION,
+ distributionMax,
+ }),
+ aggs: {
+ samples: {
+ top_hits: {
+ _source: [TRANSACTION_ID, TRACE_ID],
+ size: 10,
+ sort: {
+ _score: 'desc' as const,
},
},
},
},
},
},
- })
+ },
+ }
);
return (
@@ -148,41 +147,40 @@ export async function getBuckets({
}
async function getDistributionBuckets() {
- const response = await withApmSpan(
+ const response = await apmEventClient.search(
'get_latency_distribution_buckets',
- () =>
- apmEventClient.search({
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ],
- },
- body: {
- query: {
- bool: {
- filter: [
- ...commonFilters,
- ...getDocumentTypeFilterForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ],
- },
+ {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ],
+ },
+ body: {
+ query: {
+ bool: {
+ filter: [
+ ...commonFilters,
+ ...getDocumentTypeFilterForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ],
},
- aggs: {
- distribution: {
- histogram: getHistogramAggOptions({
- field: getTransactionDurationFieldForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- bucketSize,
- distributionMax,
- }),
- },
+ },
+ aggs: {
+ distribution: {
+ histogram: getHistogramAggOptions({
+ field: getTransactionDurationFieldForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ bucketSize,
+ distributionMax,
+ }),
},
},
- })
+ },
+ }
);
return (
diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts
index 2e86f6bb84c81..f3d4e8f6dd92d 100644
--- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts
@@ -20,7 +20,6 @@ import {
rangeQuery,
kqlQuery,
} from '../../../../server/utils/queries';
-import { withApmSpan } from '../../../utils/with_apm_span';
export async function getDistributionMax({
environment,
@@ -39,44 +38,45 @@ export async function getDistributionMax({
setup: Setup & SetupTimeRange;
searchAggregatedTransactions: boolean;
}) {
- return withApmSpan('get_latency_distribution_max', async () => {
- const { start, end, apmEventClient } = setup;
+ const { start, end, apmEventClient } = setup;
- const params = {
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- { term: { [SERVICE_NAME]: serviceName } },
- { term: { [TRANSACTION_TYPE]: transactionType } },
- { term: { [TRANSACTION_NAME]: transactionName } },
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ],
- },
+ const params = {
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ { term: { [SERVICE_NAME]: serviceName } },
+ { term: { [TRANSACTION_TYPE]: transactionType } },
+ { term: { [TRANSACTION_NAME]: transactionName } },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ],
},
- aggs: {
- stats: {
- max: {
- field: getTransactionDurationFieldForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- },
+ },
+ aggs: {
+ stats: {
+ max: {
+ field: getTransactionDurationFieldForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
},
},
},
- };
+ },
+ };
- const resp = await apmEventClient.search(params);
- return resp.aggregations?.stats.value ?? null;
- });
+ const resp = await apmEventClient.search(
+ 'get_latency_distribution_max',
+ params
+ );
+ return resp.aggregations?.stats.value ?? null;
}
diff --git a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts
index 5a58360597828..2d350090fa28b 100644
--- a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts
@@ -26,7 +26,6 @@ import {
} from '../../../lib/helpers/aggregated_transactions';
import { getBucketSize } from '../../../lib/helpers/get_bucket_size';
import { Setup, SetupTimeRange } from '../../../lib/helpers/setup_request';
-import { withApmSpan } from '../../../utils/with_apm_span';
import {
getLatencyAggregation,
getLatencyValue,
@@ -112,10 +111,10 @@ function searchLatency({
},
};
- return apmEventClient.search(params);
+ return apmEventClient.search('get_latency_charts', params);
}
-export function getLatencyTimeseries({
+export async function getLatencyTimeseries({
environment,
kuery,
serviceName,
@@ -138,40 +137,38 @@ export function getLatencyTimeseries({
start: number;
end: number;
}) {
- return withApmSpan('get_latency_charts', async () => {
- const response = await searchLatency({
- environment,
- kuery,
- serviceName,
- transactionType,
- transactionName,
- setup,
- searchAggregatedTransactions,
- latencyAggregationType,
- start,
- end,
- });
-
- if (!response.aggregations) {
- return { latencyTimeseries: [], overallAvgDuration: null };
- }
-
- return {
- overallAvgDuration:
- response.aggregations.overall_avg_duration.value || null,
- latencyTimeseries: response.aggregations.latencyTimeseries.buckets.map(
- (bucket) => {
- return {
- x: bucket.key,
- y: getLatencyValue({
- latencyAggregationType,
- aggregation: bucket.latency,
- }),
- };
- }
- ),
- };
+ const response = await searchLatency({
+ environment,
+ kuery,
+ serviceName,
+ transactionType,
+ transactionName,
+ setup,
+ searchAggregatedTransactions,
+ latencyAggregationType,
+ start,
+ end,
});
+
+ if (!response.aggregations) {
+ return { latencyTimeseries: [], overallAvgDuration: null };
+ }
+
+ return {
+ overallAvgDuration:
+ response.aggregations.overall_avg_duration.value || null,
+ latencyTimeseries: response.aggregations.latencyTimeseries.buckets.map(
+ (bucket) => {
+ return {
+ x: bucket.key,
+ y: getLatencyValue({
+ latencyAggregationType,
+ aggregation: bucket.latency,
+ }),
+ };
+ }
+ ),
+ };
}
export async function getLatencyPeriods({
diff --git a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts
index a0225eb47e584..f4d9236395252 100644
--- a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts
@@ -24,7 +24,6 @@ import {
} from '../../../lib/helpers/aggregated_transactions';
import { getBucketSize } from '../../../lib/helpers/get_bucket_size';
import { Setup, SetupTimeRange } from '../../../lib/helpers/setup_request';
-import { withApmSpan } from '../../../utils/with_apm_span';
import { getThroughputBuckets } from './transform';
export type ThroughputChartsResponse = PromiseReturnType<
@@ -96,7 +95,7 @@ function searchThroughput({
},
};
- return apmEventClient.search(params);
+ return apmEventClient.search('get_transaction_throughput_series', params);
}
export async function getThroughputCharts({
@@ -116,26 +115,24 @@ export async function getThroughputCharts({
setup: Setup & SetupTimeRange;
searchAggregatedTransactions: boolean;
}) {
- return withApmSpan('get_transaction_throughput_series', async () => {
- const { bucketSize, intervalString } = getBucketSize(setup);
+ const { bucketSize, intervalString } = getBucketSize(setup);
- const response = await searchThroughput({
- environment,
- kuery,
- serviceName,
- transactionType,
- transactionName,
- setup,
- searchAggregatedTransactions,
- intervalString,
- });
-
- return {
- throughputTimeseries: getThroughputBuckets({
- throughputResultBuckets: response.aggregations?.throughput.buckets,
- bucketSize,
- setupTimeRange: setup,
- }),
- };
+ const response = await searchThroughput({
+ environment,
+ kuery,
+ serviceName,
+ transactionType,
+ transactionName,
+ setup,
+ searchAggregatedTransactions,
+ intervalString,
});
+
+ return {
+ throughputTimeseries: getThroughputBuckets({
+ throughputResultBuckets: response.aggregations?.throughput.buckets,
+ bucketSize,
+ setupTimeRange: setup,
+ }),
+ };
}
diff --git a/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts
index 6987ef0757734..c928b00cefb63 100644
--- a/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts
@@ -13,9 +13,8 @@ import { rangeQuery } from '../../../../server/utils/queries';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
import { ProcessorEvent } from '../../../../common/processor_event';
import { asMutableArray } from '../../../../common/utils/as_mutable_array';
-import { withApmSpan } from '../../../utils/with_apm_span';
-export function getTransaction({
+export async function getTransaction({
transactionId,
traceId,
setup,
@@ -24,27 +23,25 @@ export function getTransaction({
traceId?: string;
setup: Setup | (Setup & SetupTimeRange);
}) {
- return withApmSpan('get_transaction', async () => {
- const { apmEventClient } = setup;
+ const { apmEventClient } = setup;
- const resp = await apmEventClient.search({
- apm: {
- events: [ProcessorEvent.transaction],
- },
- body: {
- size: 1,
- query: {
- bool: {
- filter: asMutableArray([
- { term: { [TRANSACTION_ID]: transactionId } },
- ...(traceId ? [{ term: { [TRACE_ID]: traceId } }] : []),
- ...('start' in setup ? rangeQuery(setup.start, setup.end) : []),
- ]),
- },
+ const resp = await apmEventClient.search('get_transaction', {
+ apm: {
+ events: [ProcessorEvent.transaction],
+ },
+ body: {
+ size: 1,
+ query: {
+ bool: {
+ filter: asMutableArray([
+ { term: { [TRANSACTION_ID]: transactionId } },
+ ...(traceId ? [{ term: { [TRACE_ID]: traceId } }] : []),
+ ...('start' in setup ? rangeQuery(setup.start, setup.end) : []),
+ ]),
},
},
- });
-
- return resp.hits.hits[0]?._source;
+ },
});
+
+ return resp.hits.hits[0]?._source;
}
diff --git a/x-pack/plugins/apm/server/lib/transactions/get_transaction_by_trace/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_transaction_by_trace/index.ts
index dfdad2f59a848..568ce16e7aedc 100644
--- a/x-pack/plugins/apm/server/lib/transactions/get_transaction_by_trace/index.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/get_transaction_by_trace/index.ts
@@ -11,40 +11,43 @@ import {
} from '../../../../common/elasticsearch_fieldnames';
import { Setup } from '../../helpers/setup_request';
import { ProcessorEvent } from '../../../../common/processor_event';
-import { withApmSpan } from '../../../utils/with_apm_span';
-export function getRootTransactionByTraceId(traceId: string, setup: Setup) {
- return withApmSpan('get_root_transaction_by_trace_id', async () => {
- const { apmEventClient } = setup;
+export async function getRootTransactionByTraceId(
+ traceId: string,
+ setup: Setup
+) {
+ const { apmEventClient } = setup;
- const params = {
- apm: {
- events: [ProcessorEvent.transaction as const],
- },
- body: {
- size: 1,
- query: {
- bool: {
- should: [
- {
- constant_score: {
- filter: {
- bool: {
- must_not: { exists: { field: PARENT_ID } },
- },
+ const params = {
+ apm: {
+ events: [ProcessorEvent.transaction as const],
+ },
+ body: {
+ size: 1,
+ query: {
+ bool: {
+ should: [
+ {
+ constant_score: {
+ filter: {
+ bool: {
+ must_not: { exists: { field: PARENT_ID } },
},
},
},
- ],
- filter: [{ term: { [TRACE_ID]: traceId } }],
- },
+ },
+ ],
+ filter: [{ term: { [TRACE_ID]: traceId } }],
},
},
- };
+ },
+ };
- const resp = await apmEventClient.search(params);
- return {
- transaction: resp.hits.hits[0]?._source,
- };
- });
+ const resp = await apmEventClient.search(
+ 'get_root_transaction_by_trace_id',
+ params
+ );
+ return {
+ transaction: resp.hits.hits[0]?._source,
+ };
}
diff --git a/x-pack/plugins/apm/server/utils/test_helpers.tsx b/x-pack/plugins/apm/server/utils/test_helpers.tsx
index 6252c33c5994d..9c63f0140bdf2 100644
--- a/x-pack/plugins/apm/server/utils/test_helpers.tsx
+++ b/x-pack/plugins/apm/server/utils/test_helpers.tsx
@@ -109,7 +109,7 @@ export async function inspectSearchParams(
}
return {
- params: spy.mock.calls[0][0],
+ params: spy.mock.calls[0][1],
response,
error,
spy,
diff --git a/x-pack/plugins/apm/server/utils/with_apm_span.ts b/x-pack/plugins/apm/server/utils/with_apm_span.ts
index 9762a7213d0a2..1343970f04a3f 100644
--- a/x-pack/plugins/apm/server/utils/with_apm_span.ts
+++ b/x-pack/plugins/apm/server/utils/with_apm_span.ts
@@ -13,7 +13,7 @@ export function withApmSpan(
const options = parseSpanOptions(optionsOrName);
const optionsWithDefaults = {
- type: 'plugin:apm',
+ ...(options.intercept ? {} : { type: 'plugin:apm' }),
...options,
labels: {
plugin: 'apm',
From 767b67d0522ec00865a7746d3ec37cd5de077821 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Casper=20H=C3=BCbertz?=
Date: Mon, 14 Jun 2021 18:49:58 +0200
Subject: [PATCH 13/91] [APM] Change View full trace button fill (#102066)
---
.../WaterfallWithSummmary/MaybeViewTraceLink.tsx | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/MaybeViewTraceLink.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/MaybeViewTraceLink.tsx
index 4017495dd3b5d..11a0cc1234f42 100644
--- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/MaybeViewTraceLink.tsx
+++ b/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/MaybeViewTraceLink.tsx
@@ -45,7 +45,7 @@ export const MaybeViewTraceLink = ({
}
)}
>
-
+
{viewFullTraceButtonLabel}
@@ -67,7 +67,7 @@ export const MaybeViewTraceLink = ({
}
)}
>
-
+
{viewFullTraceButtonLabel}
@@ -92,7 +92,9 @@ export const MaybeViewTraceLink = ({
environment={nextEnvironment}
latencyAggregationType={latencyAggregationType}
>
- {viewFullTraceButtonLabel}
+
+ {viewFullTraceButtonLabel}
+
);
From 4fb0c943a6b5048c6033f5d8d41729aa2eb4f839 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ece=20=C3=96zalp?=
Date: Mon, 14 Jun 2021 13:31:20 -0400
Subject: [PATCH 14/91] [Security Solution] Refactor rules for modularization
by updating bulkCreate and wrapHits methods (#101544)
creates bulkCreate and wrapHits factories for the modularization of the detection engine
Co-authored-by: Marshall Main
---
.../rules_notification_alert_type.ts | 1 -
.../schedule_notification_actions.ts | 3 +-
.../__mocks__/build_rule_message.mock.ts | 15 +
.../signals/bulk_create_factory.ts | 100 ++++++
.../signals/bulk_create_ml_signals.ts | 15 +-
.../signals/executors/eql.test.ts | 39 +--
.../detection_engine/signals/executors/eql.ts | 21 +-
.../signals/executors/ml.test.ts | 30 +-
.../detection_engine/signals/executors/ml.ts | 21 +-
.../signals/executors/query.ts | 12 +-
.../signals/executors/threat_match.ts | 12 +-
.../signals/executors/threshold.test.ts | 20 +-
.../signals/executors/threshold.ts | 27 +-
.../signals/filter_duplicate_signals.test.ts | 46 +++
.../signals/filter_duplicate_signals.ts | 14 +
.../signals/search_after_bulk_create.test.ts | 67 ++--
.../signals/search_after_bulk_create.ts | 22 +-
.../signals/signal_rule_alert_type.test.ts | 30 +-
.../signals/signal_rule_alert_type.ts | 37 +-
.../signals/single_bulk_create.test.ts | 318 ------------------
.../signals/single_bulk_create.ts | 227 -------------
.../threat_mapping/create_threat_signal.ts | 6 +-
.../threat_mapping/create_threat_signals.ts | 7 +-
.../signals/threat_mapping/types.ts | 8 +-
.../signals/threat_mapping/utils.test.ts | 37 ++
.../signals/threat_mapping/utils.ts | 3 +
.../bulk_create_threshold_signals.ts | 146 ++++----
.../lib/detection_engine/signals/types.ts | 14 +-
.../detection_engine/signals/utils.test.ts | 13 +-
.../lib/detection_engine/signals/utils.ts | 28 +-
.../signals/wrap_hits_factory.ts | 35 ++
.../security_solution/server/lib/types.ts | 3 +
.../security_solution/server/plugin.ts | 2 +
33 files changed, 543 insertions(+), 836 deletions(-)
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/build_rule_message.mock.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_factory.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.test.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts
delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts
delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts
index 6f8dac5b49b31..a4863e577c6bc 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts
@@ -91,7 +91,6 @@ export const rulesNotificationAlertType = ({
signalsCount,
resultsLink,
ruleParams,
- // @ts-expect-error @elastic/elasticsearch _source is optional
signals,
});
}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts
index e7db10380eea1..bfb96e97edf11 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts
@@ -8,7 +8,6 @@
import { mapKeys, snakeCase } from 'lodash/fp';
import { AlertInstance } from '../../../../../alerting/server';
import { RuleParams } from '../schemas/rule_schemas';
-import { SignalSource } from '../signals/types';
export type NotificationRuleTypeParams = RuleParams & {
name: string;
@@ -20,7 +19,7 @@ interface ScheduleNotificationActions {
signalsCount: number;
resultsLink: string;
ruleParams: NotificationRuleTypeParams;
- signals: SignalSource[];
+ signals: unknown[];
}
export const scheduleNotificationActions = ({
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/build_rule_message.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/build_rule_message.mock.ts
new file mode 100644
index 0000000000000..f43142d1d0264
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/build_rule_message.mock.ts
@@ -0,0 +1,15 @@
+/*
+ * 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 { buildRuleMessageFactory } from '../rule_messages';
+
+export const mockBuildRuleMessage = buildRuleMessageFactory({
+ id: 'fake id',
+ ruleId: 'fake rule id',
+ index: 'fakeindex',
+ name: 'fake name',
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_factory.ts
new file mode 100644
index 0000000000000..f518ac2386d0b
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_factory.ts
@@ -0,0 +1,100 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { performance } from 'perf_hooks';
+import { countBy, isEmpty, get } from 'lodash';
+import { ElasticsearchClient, Logger } from 'kibana/server';
+import { BuildRuleMessage } from './rule_messages';
+import { RefreshTypes } from '../types';
+import { BaseHit } from '../../../../common/detection_engine/types';
+import { errorAggregator, makeFloatString } from './utils';
+
+export interface GenericBulkCreateResponse {
+ success: boolean;
+ bulkCreateDuration: string;
+ createdItemsCount: number;
+ createdItems: Array;
+ errors: string[];
+}
+
+export const bulkCreateFactory = (
+ logger: Logger,
+ esClient: ElasticsearchClient,
+ buildRuleMessage: BuildRuleMessage,
+ refreshForBulkCreate: RefreshTypes
+) => async (wrappedDocs: Array>): Promise> => {
+ if (wrappedDocs.length === 0) {
+ return {
+ errors: [],
+ success: true,
+ bulkCreateDuration: '0',
+ createdItemsCount: 0,
+ createdItems: [],
+ };
+ }
+
+ const bulkBody = wrappedDocs.flatMap((wrappedDoc) => [
+ {
+ create: {
+ _index: wrappedDoc._index,
+ _id: wrappedDoc._id,
+ },
+ },
+ wrappedDoc._source,
+ ]);
+ const start = performance.now();
+
+ const { body: response } = await esClient.bulk({
+ refresh: refreshForBulkCreate,
+ body: bulkBody,
+ });
+
+ const end = performance.now();
+ logger.debug(
+ buildRuleMessage(
+ `individual bulk process time took: ${makeFloatString(end - start)} milliseconds`
+ )
+ );
+ logger.debug(buildRuleMessage(`took property says bulk took: ${response.took} milliseconds`));
+ const createdItems = wrappedDocs
+ .map((doc, index) => ({
+ _id: response.items[index].create?._id ?? '',
+ _index: response.items[index].create?._index ?? '',
+ ...doc._source,
+ }))
+ .filter((_, index) => get(response.items[index], 'create.status') === 201);
+ const createdItemsCount = createdItems.length;
+ const duplicateSignalsCount = countBy(response.items, 'create.status')['409'];
+ const errorCountByMessage = errorAggregator(response, [409]);
+
+ logger.debug(buildRuleMessage(`bulk created ${createdItemsCount} signals`));
+ if (duplicateSignalsCount > 0) {
+ logger.debug(buildRuleMessage(`ignored ${duplicateSignalsCount} duplicate signals`));
+ }
+ if (!isEmpty(errorCountByMessage)) {
+ logger.error(
+ buildRuleMessage(
+ `[-] bulkResponse had errors with responses of: ${JSON.stringify(errorCountByMessage)}`
+ )
+ );
+ return {
+ errors: Object.keys(errorCountByMessage),
+ success: false,
+ bulkCreateDuration: makeFloatString(end - start),
+ createdItemsCount,
+ createdItems,
+ };
+ } else {
+ return {
+ errors: [],
+ success: true,
+ bulkCreateDuration: makeFloatString(end - start),
+ createdItemsCount,
+ createdItems,
+ };
+ }
+};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts
index 00ac40fa7e27c..ebb4462817eab 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts
@@ -14,11 +14,10 @@ import {
AlertInstanceState,
AlertServices,
} from '../../../../../alerting/server';
-import { RefreshTypes } from '../types';
-import { singleBulkCreate, SingleBulkCreateResponse } from './single_bulk_create';
+import { GenericBulkCreateResponse } from './bulk_create_factory';
import { AnomalyResults, Anomaly } from '../../machine_learning';
import { BuildRuleMessage } from './rule_messages';
-import { AlertAttributes } from './types';
+import { AlertAttributes, BulkCreate, WrapHits } from './types';
import { MachineLearningRuleParams } from '../schemas/rule_schemas';
interface BulkCreateMlSignalsParams {
@@ -28,8 +27,9 @@ interface BulkCreateMlSignalsParams {
logger: Logger;
id: string;
signalsIndex: string;
- refresh: RefreshTypes;
buildRuleMessage: BuildRuleMessage;
+ bulkCreate: BulkCreate;
+ wrapHits: WrapHits;
}
interface EcsAnomaly extends Anomaly {
@@ -85,9 +85,10 @@ const transformAnomalyResultsToEcs = (
export const bulkCreateMlSignals = async (
params: BulkCreateMlSignalsParams
-): Promise => {
+): Promise> => {
const anomalyResults = params.someResult;
const ecsResults = transformAnomalyResultsToEcs(anomalyResults);
- const buildRuleMessage = params.buildRuleMessage;
- return singleBulkCreate({ ...params, filteredEvents: ecsResults, buildRuleMessage });
+
+ const wrappedDocs = params.wrapHits(ecsResults.hits.hits);
+ return params.bulkCreate(wrappedDocs);
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts
index f8f77bd2bf6e6..947e7d573173e 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts
@@ -7,7 +7,6 @@
import { loggingSystemMock } from 'src/core/server/mocks';
import { alertsMock, AlertServicesMock } from '../../../../../../alerting/server/mocks';
-import { RuleStatusService } from '../rule_status_service';
import { eqlExecutor } from './eql';
import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
import { getEntryListMock } from '../../../../../../lists/common/schemas/types/entry_list.mock';
@@ -23,7 +22,6 @@ describe('eql_executor', () => {
const version = '8.0.0';
let logger: ReturnType;
let alertServices: AlertServicesMock;
- let ruleStatusService: Record;
(getIndexVersion as jest.Mock).mockReturnValue(SIGNALS_TEMPLATE_VERSION);
const eqlSO = {
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
@@ -51,17 +49,11 @@ describe('eql_executor', () => {
beforeEach(() => {
alertServices = alertsMock.createAlertServices();
logger = loggingSystemMock.createLogger();
- ruleStatusService = {
- success: jest.fn(),
- find: jest.fn(),
- goingToRun: jest.fn(),
- error: jest.fn(),
- partialFailure: jest.fn(),
- };
alertServices.scopedClusterClient.asCurrentUser.transport.request.mockResolvedValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({
hits: {
total: { value: 10 },
+ events: [],
},
})
);
@@ -70,25 +62,16 @@ describe('eql_executor', () => {
describe('eqlExecutor', () => {
it('should set a warning when exception list for eql rule contains value list exceptions', async () => {
const exceptionItems = [getExceptionListItemSchemaMock({ entries: [getEntryListMock()] })];
- try {
- await eqlExecutor({
- rule: eqlSO,
- exceptionItems,
- ruleStatusService: (ruleStatusService as unknown) as RuleStatusService,
- services: alertServices,
- version,
- logger,
- refresh: false,
- searchAfterSize,
- });
- } catch (err) {
- // eqlExecutor will throw until we have an EQL response mock that conforms to the
- // expected EQL response format, so just catch the error and check the status service
- }
- expect(ruleStatusService.partialFailure).toHaveBeenCalled();
- expect(ruleStatusService.partialFailure.mock.calls[0][0]).toContain(
- 'Exceptions that use "is in list" or "is not in list" operators are not applied to EQL rules'
- );
+ const response = await eqlExecutor({
+ rule: eqlSO,
+ exceptionItems,
+ services: alertServices,
+ version,
+ logger,
+ searchAfterSize,
+ bulkCreate: jest.fn(),
+ });
+ expect(response.warningMessages.length).toEqual(1);
});
});
});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts
index 8e2f5e92ae502..28d1f3e19baee 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts
@@ -7,9 +7,9 @@
import { ApiResponse } from '@elastic/elasticsearch';
import { performance } from 'perf_hooks';
-import { Logger } from 'src/core/server';
import { SavedObject } from 'src/core/types';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
+import { Logger } from 'src/core/server';
import {
AlertInstanceContext,
AlertInstanceState,
@@ -21,13 +21,12 @@ import { isOutdated } from '../../migrations/helpers';
import { getIndexVersion } from '../../routes/index/get_index_version';
import { MIN_EQL_RULE_INDEX_VERSION } from '../../routes/index/get_signals_template';
import { EqlRuleParams } from '../../schemas/rule_schemas';
-import { RefreshTypes } from '../../types';
import { buildSignalFromEvent, buildSignalGroupFromSequence } from '../build_bulk_body';
import { getInputIndex } from '../get_input_output_index';
-import { RuleStatusService } from '../rule_status_service';
-import { bulkInsertSignals, filterDuplicateSignals } from '../single_bulk_create';
+import { filterDuplicateSignals } from '../filter_duplicate_signals';
import {
AlertAttributes,
+ BulkCreate,
EqlSignalSearchResponse,
SearchAfterAndBulkCreateReturnType,
WrappedSignalHit,
@@ -37,26 +36,24 @@ import { createSearchAfterReturnType, makeFloatString, wrapSignal } from '../uti
export const eqlExecutor = async ({
rule,
exceptionItems,
- ruleStatusService,
services,
version,
- searchAfterSize,
logger,
- refresh,
+ searchAfterSize,
+ bulkCreate,
}: {
rule: SavedObject>;
exceptionItems: ExceptionListItemSchema[];
- ruleStatusService: RuleStatusService;
services: AlertServices;
version: string;
- searchAfterSize: number;
logger: Logger;
- refresh: RefreshTypes;
+ searchAfterSize: number;
+ bulkCreate: BulkCreate;
}): Promise => {
const result = createSearchAfterReturnType();
const ruleParams = rule.attributes.params;
if (hasLargeValueItem(exceptionItems)) {
- await ruleStatusService.partialFailure(
+ result.warningMessages.push(
'Exceptions that use "is in list" or "is not in list" operators are not applied to EQL rules'
);
result.warning = true;
@@ -125,7 +122,7 @@ export const eqlExecutor = async ({
}
if (newSignals.length > 0) {
- const insertResult = await bulkInsertSignals(newSignals, logger, services, refresh);
+ const insertResult = await bulkCreate(newSignals);
result.bulkCreateTimes.push(insertResult.bulkCreateDuration);
result.createdSignalsCount += insertResult.createdItemsCount;
result.createdSignals = insertResult.createdItems;
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts
index e157750a7d51b..25a9d2c3f510f 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts
@@ -7,7 +7,6 @@
import { loggingSystemMock } from 'src/core/server/mocks';
import { alertsMock, AlertServicesMock } from '../../../../../../alerting/server/mocks';
-import { RuleStatusService } from '../rule_status_service';
import { mlExecutor } from './ml';
import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
import { getMlRuleParams } from '../../schemas/rule_schemas.mock';
@@ -17,7 +16,6 @@ import { findMlSignals } from '../find_ml_signals';
import { bulkCreateMlSignals } from '../bulk_create_ml_signals';
import { mlPluginServerMock } from '../../../../../../ml/server/mocks';
import { sampleRuleSO } from '../__mocks__/es_results';
-import { getRuleStatusServiceMock } from '../rule_status_service.mock';
jest.mock('../find_ml_signals');
jest.mock('../bulk_create_ml_signals');
@@ -25,7 +23,6 @@ jest.mock('../bulk_create_ml_signals');
describe('ml_executor', () => {
let jobsSummaryMock: jest.Mock;
let mlMock: ReturnType;
- let ruleStatusService: ReturnType;
const exceptionItems = [getExceptionListItemSchemaMock()];
let logger: ReturnType;
let alertServices: AlertServicesMock;
@@ -45,7 +42,6 @@ describe('ml_executor', () => {
mlMock.jobServiceProvider.mockReturnValue({
jobsSummary: jobsSummaryMock,
});
- ruleStatusService = getRuleStatusServiceMock();
(findMlSignals as jest.Mock).mockResolvedValue({
_shards: {},
hits: {
@@ -66,35 +62,32 @@ describe('ml_executor', () => {
rule: mlSO,
ml: undefined,
exceptionItems,
- ruleStatusService,
services: alertServices,
logger,
- refresh: false,
buildRuleMessage,
listClient: getListClientMock(),
+ bulkCreate: jest.fn(),
+ wrapHits: jest.fn(),
})
).rejects.toThrow('ML plugin unavailable during rule execution');
});
it('should record a partial failure if Machine learning job summary was null', async () => {
jobsSummaryMock.mockResolvedValue([]);
- await mlExecutor({
+ const response = await mlExecutor({
rule: mlSO,
ml: mlMock,
exceptionItems,
- ruleStatusService,
services: alertServices,
logger,
- refresh: false,
buildRuleMessage,
listClient: getListClientMock(),
+ bulkCreate: jest.fn(),
+ wrapHits: jest.fn(),
});
expect(logger.warn).toHaveBeenCalled();
expect(logger.warn.mock.calls[0][0]).toContain('Machine learning job(s) are not started');
- expect(ruleStatusService.partialFailure).toHaveBeenCalled();
- expect(ruleStatusService.partialFailure.mock.calls[0][0]).toContain(
- 'Machine learning job(s) are not started'
- );
+ expect(response.warningMessages.length).toEqual(1);
});
it('should record a partial failure if Machine learning job was not started', async () => {
@@ -106,22 +99,19 @@ describe('ml_executor', () => {
},
]);
- await mlExecutor({
+ const response = await mlExecutor({
rule: mlSO,
ml: mlMock,
exceptionItems,
- ruleStatusService: (ruleStatusService as unknown) as RuleStatusService,
services: alertServices,
logger,
- refresh: false,
buildRuleMessage,
listClient: getListClientMock(),
+ bulkCreate: jest.fn(),
+ wrapHits: jest.fn(),
});
expect(logger.warn).toHaveBeenCalled();
expect(logger.warn.mock.calls[0][0]).toContain('Machine learning job(s) are not started');
- expect(ruleStatusService.partialFailure).toHaveBeenCalled();
- expect(ruleStatusService.partialFailure.mock.calls[0][0]).toContain(
- 'Machine learning job(s) are not started'
- );
+ expect(response.warningMessages.length).toEqual(1);
});
});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts
index 28703046289f5..f5c7d8822b51f 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts
@@ -17,13 +17,11 @@ import { ListClient } from '../../../../../../lists/server';
import { isJobStarted } from '../../../../../common/machine_learning/helpers';
import { SetupPlugins } from '../../../../plugin';
import { MachineLearningRuleParams } from '../../schemas/rule_schemas';
-import { RefreshTypes } from '../../types';
import { bulkCreateMlSignals } from '../bulk_create_ml_signals';
import { filterEventsAgainstList } from '../filters/filter_events_against_list';
import { findMlSignals } from '../find_ml_signals';
import { BuildRuleMessage } from '../rule_messages';
-import { RuleStatusService } from '../rule_status_service';
-import { AlertAttributes } from '../types';
+import { AlertAttributes, BulkCreate, WrapHits } from '../types';
import { createErrorsFromShard, createSearchAfterReturnType, mergeReturns } from '../utils';
export const mlExecutor = async ({
@@ -31,21 +29,21 @@ export const mlExecutor = async ({
ml,
listClient,
exceptionItems,
- ruleStatusService,
services,
logger,
- refresh,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
}: {
rule: SavedObject>;
ml: SetupPlugins['ml'];
listClient: ListClient;
exceptionItems: ExceptionListItemSchema[];
- ruleStatusService: RuleStatusService;
services: AlertServices;
logger: Logger;
- refresh: RefreshTypes;
buildRuleMessage: BuildRuleMessage;
+ bulkCreate: BulkCreate;
+ wrapHits: WrapHits;
}) => {
const result = createSearchAfterReturnType();
const ruleParams = rule.attributes.params;
@@ -67,7 +65,7 @@ export const mlExecutor = async ({
jobSummaries.length < 1 ||
jobSummaries.some((job) => !isJobStarted(job.jobState, job.datafeedState))
) {
- const errorMessage = buildRuleMessage(
+ const warningMessage = buildRuleMessage(
'Machine learning job(s) are not started:',
...jobSummaries.map((job) =>
[
@@ -77,9 +75,9 @@ export const mlExecutor = async ({
].join(', ')
)
);
- logger.warn(errorMessage);
+ result.warningMessages.push(warningMessage);
+ logger.warn(warningMessage);
result.warning = true;
- await ruleStatusService.partialFailure(errorMessage);
}
const anomalyResults = await findMlSignals({
@@ -120,8 +118,9 @@ export const mlExecutor = async ({
logger,
id: rule.id,
signalsIndex: ruleParams.outputIndex,
- refresh,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
// The legacy ES client does not define failures when it can be present on the structure, hence why I have the & { failures: [] }
const shardFailures =
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts
index 05e2e3056e99e..9d76a06afa275 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts
@@ -14,11 +14,10 @@ import {
AlertServices,
} from '../../../../../../alerting/server';
import { ListClient } from '../../../../../../lists/server';
-import { RefreshTypes } from '../../types';
import { getFilter } from '../get_filter';
import { getInputIndex } from '../get_input_output_index';
import { searchAfterAndBulkCreate } from '../search_after_bulk_create';
-import { AlertAttributes, RuleRangeTuple } from '../types';
+import { AlertAttributes, RuleRangeTuple, BulkCreate, WrapHits } from '../types';
import { TelemetryEventsSender } from '../../../telemetry/sender';
import { BuildRuleMessage } from '../rule_messages';
import { QueryRuleParams, SavedQueryRuleParams } from '../../schemas/rule_schemas';
@@ -32,9 +31,10 @@ export const queryExecutor = async ({
version,
searchAfterSize,
logger,
- refresh,
eventsTelemetry,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
}: {
rule: SavedObject>;
tuples: RuleRangeTuple[];
@@ -44,9 +44,10 @@ export const queryExecutor = async ({
version: string;
searchAfterSize: number;
logger: Logger;
- refresh: RefreshTypes;
eventsTelemetry: TelemetryEventsSender | undefined;
buildRuleMessage: BuildRuleMessage;
+ bulkCreate: BulkCreate;
+ wrapHits: WrapHits;
}) => {
const ruleParams = rule.attributes.params;
const inputIndex = await getInputIndex(services, version, ruleParams.index);
@@ -74,7 +75,8 @@ export const queryExecutor = async ({
signalsIndex: ruleParams.outputIndex,
filter: esFilter,
pageSize: searchAfterSize,
- refresh,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts
index 10b4ce939ca3a..078eb8362069c 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts
@@ -14,9 +14,8 @@ import {
AlertServices,
} from '../../../../../../alerting/server';
import { ListClient } from '../../../../../../lists/server';
-import { RefreshTypes } from '../../types';
import { getInputIndex } from '../get_input_output_index';
-import { RuleRangeTuple, AlertAttributes } from '../types';
+import { RuleRangeTuple, AlertAttributes, BulkCreate, WrapHits } from '../types';
import { TelemetryEventsSender } from '../../../telemetry/sender';
import { BuildRuleMessage } from '../rule_messages';
import { createThreatSignals } from '../threat_mapping/create_threat_signals';
@@ -31,9 +30,10 @@ export const threatMatchExecutor = async ({
version,
searchAfterSize,
logger,
- refresh,
eventsTelemetry,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
}: {
rule: SavedObject>;
tuples: RuleRangeTuple[];
@@ -43,9 +43,10 @@ export const threatMatchExecutor = async ({
version: string;
searchAfterSize: number;
logger: Logger;
- refresh: RefreshTypes;
eventsTelemetry: TelemetryEventsSender | undefined;
buildRuleMessage: BuildRuleMessage;
+ bulkCreate: BulkCreate;
+ wrapHits: WrapHits;
}) => {
const ruleParams = rule.attributes.params;
const inputIndex = await getInputIndex(services, version, ruleParams.index);
@@ -67,7 +68,6 @@ export const threatMatchExecutor = async ({
outputIndex: ruleParams.outputIndex,
ruleSO: rule,
searchAfterSize,
- refresh,
threatFilters: ruleParams.threatFilters ?? [],
threatQuery: ruleParams.threatQuery,
threatLanguage: ruleParams.threatLanguage,
@@ -76,5 +76,7 @@ export const threatMatchExecutor = async ({
threatIndicatorPath: ruleParams.threatIndicatorPath,
concurrentSearches: ruleParams.concurrentSearches ?? 1,
itemsPerSearch: ruleParams.itemsPerSearch ?? 9000,
+ bulkCreate,
+ wrapHits,
});
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts
index 5d62b28b73ae8..f03e8b8a147ae 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts
@@ -7,7 +7,6 @@
import { loggingSystemMock } from 'src/core/server/mocks';
import { alertsMock, AlertServicesMock } from '../../../../../../alerting/server/mocks';
-import { RuleStatusService } from '../rule_status_service';
import { thresholdExecutor } from './threshold';
import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
import { getEntryListMock } from '../../../../../../lists/common/schemas/types/entry_list.mock';
@@ -18,7 +17,6 @@ describe('threshold_executor', () => {
const version = '8.0.0';
let logger: ReturnType;
let alertServices: AlertServicesMock;
- let ruleStatusService: Record;
const thresholdSO = {
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
type: 'alert',
@@ -50,34 +48,24 @@ describe('threshold_executor', () => {
beforeEach(() => {
alertServices = alertsMock.createAlertServices();
logger = loggingSystemMock.createLogger();
- ruleStatusService = {
- success: jest.fn(),
- find: jest.fn(),
- goingToRun: jest.fn(),
- error: jest.fn(),
- partialFailure: jest.fn(),
- };
});
describe('thresholdExecutor', () => {
it('should set a warning when exception list for threshold rule contains value list exceptions', async () => {
const exceptionItems = [getExceptionListItemSchemaMock({ entries: [getEntryListMock()] })];
- await thresholdExecutor({
+ const response = await thresholdExecutor({
rule: thresholdSO,
tuples: [],
exceptionItems,
- ruleStatusService: (ruleStatusService as unknown) as RuleStatusService,
services: alertServices,
version,
logger,
- refresh: false,
buildRuleMessage,
startedAt: new Date(),
+ bulkCreate: jest.fn(),
+ wrapHits: jest.fn(),
});
- expect(ruleStatusService.partialFailure).toHaveBeenCalled();
- expect(ruleStatusService.partialFailure.mock.calls[0][0]).toContain(
- 'Exceptions that use "is in list" or "is not in list" operators are not applied to Threshold rules'
- );
+ expect(response.warningMessages.length).toEqual(1);
});
});
});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts
index fa0986044e250..5e23128c9c148 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts
@@ -15,51 +15,55 @@ import {
} from '../../../../../../alerting/server';
import { hasLargeValueItem } from '../../../../../common/detection_engine/utils';
import { ThresholdRuleParams } from '../../schemas/rule_schemas';
-import { RefreshTypes } from '../../types';
import { getFilter } from '../get_filter';
import { getInputIndex } from '../get_input_output_index';
-import { BuildRuleMessage } from '../rule_messages';
-import { RuleStatusService } from '../rule_status_service';
import {
bulkCreateThresholdSignals,
findThresholdSignals,
getThresholdBucketFilters,
getThresholdSignalHistory,
} from '../threshold';
-import { AlertAttributes, RuleRangeTuple, SearchAfterAndBulkCreateReturnType } from '../types';
+import {
+ AlertAttributes,
+ BulkCreate,
+ RuleRangeTuple,
+ SearchAfterAndBulkCreateReturnType,
+ WrapHits,
+} from '../types';
import {
createSearchAfterReturnType,
createSearchAfterReturnTypeFromResponse,
mergeReturns,
} from '../utils';
+import { BuildRuleMessage } from '../rule_messages';
export const thresholdExecutor = async ({
rule,
tuples,
exceptionItems,
- ruleStatusService,
services,
version,
logger,
- refresh,
buildRuleMessage,
startedAt,
+ bulkCreate,
+ wrapHits,
}: {
rule: SavedObject>;
tuples: RuleRangeTuple[];
exceptionItems: ExceptionListItemSchema[];
- ruleStatusService: RuleStatusService;
services: AlertServices;
version: string;
logger: Logger;
- refresh: RefreshTypes;
buildRuleMessage: BuildRuleMessage;
startedAt: Date;
+ bulkCreate: BulkCreate;
+ wrapHits: WrapHits;
}): Promise => {
let result = createSearchAfterReturnType();
const ruleParams = rule.attributes.params;
if (hasLargeValueItem(exceptionItems)) {
- await ruleStatusService.partialFailure(
+ result.warningMessages.push(
'Exceptions that use "is in list" or "is not in list" operators are not applied to Threshold rules'
);
result.warning = true;
@@ -126,14 +130,13 @@ export const thresholdExecutor = async ({
filter: esFilter,
services,
logger,
- id: rule.id,
inputIndexPattern: inputIndex,
signalsIndex: ruleParams.outputIndex,
startedAt,
from: tuple.from.toDate(),
- refresh,
thresholdSignalHistory,
- buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
result = mergeReturns([
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.test.ts
new file mode 100644
index 0000000000000..5c4af83c3b03e
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.test.ts
@@ -0,0 +1,46 @@
+/*
+ * 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 { filterDuplicateSignals } from './filter_duplicate_signals';
+import { sampleWrappedSignalHit } from './__mocks__/es_results';
+
+const mockRuleId1 = 'aaaaaaaa';
+const mockRuleId2 = 'bbbbbbbb';
+const mockRuleId3 = 'cccccccc';
+
+const createWrappedSignalHitWithRuleId = (ruleId: string) => {
+ const mockSignal = sampleWrappedSignalHit();
+ return {
+ ...mockSignal,
+ _source: {
+ ...mockSignal._source,
+ signal: {
+ ...mockSignal._source.signal,
+ ancestors: [
+ {
+ ...mockSignal._source.signal.ancestors[0],
+ rule: ruleId,
+ },
+ ],
+ },
+ },
+ };
+};
+const mockSignals = [
+ createWrappedSignalHitWithRuleId(mockRuleId1),
+ createWrappedSignalHitWithRuleId(mockRuleId2),
+];
+
+describe('filterDuplicateSignals', () => {
+ it('filters duplicate signals', () => {
+ expect(filterDuplicateSignals(mockRuleId1, mockSignals).length).toEqual(1);
+ });
+
+ it('does not filter non-duplicate signals', () => {
+ expect(filterDuplicateSignals(mockRuleId3, mockSignals).length).toEqual(2);
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts
new file mode 100644
index 0000000000000..a648c05306289
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.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.
+ */
+
+import { WrappedSignalHit } from './types';
+
+export const filterDuplicateSignals = (ruleId: string, signals: WrappedSignalHit[]) => {
+ return signals.filter(
+ (doc) => !doc._source.signal?.ancestors.some((ancestor) => ancestor.rule === ruleId)
+ );
+};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts
index 52c887c3ca55a..e4eb7e854f670 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts
@@ -16,29 +16,28 @@ import {
sampleDocWithSortId,
} from './__mocks__/es_results';
import { searchAfterAndBulkCreate } from './search_after_bulk_create';
-import { buildRuleMessageFactory } from './rule_messages';
import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants';
import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks';
import uuid from 'uuid';
import { listMock } from '../../../../../lists/server/mocks';
import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
-import { BulkResponse, RuleRangeTuple } from './types';
+import { BulkCreate, BulkResponse, RuleRangeTuple, WrapHits } from './types';
import type { SearchListItemArraySchema } from '@kbn/securitysolution-io-ts-list-types';
import { getSearchListItemResponseMock } from '../../../../../lists/common/schemas/response/search_list_item_schema.mock';
import { getRuleRangeTuples } from './utils';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';
import { getQueryRuleParams } from '../schemas/rule_schemas.mock';
+import { bulkCreateFactory } from './bulk_create_factory';
+import { wrapHitsFactory } from './wrap_hits_factory';
+import { mockBuildRuleMessage } from './__mocks__/build_rule_message.mock';
-const buildRuleMessage = buildRuleMessageFactory({
- id: 'fake id',
- ruleId: 'fake rule id',
- index: 'fakeindex',
- name: 'fake name',
-});
+const buildRuleMessage = mockBuildRuleMessage;
describe('searchAfterAndBulkCreate', () => {
let mockService: AlertServicesMock;
+ let bulkCreate: BulkCreate;
+ let wrapHits: WrapHits;
let inputIndexPattern: string[] = [];
let listClient = listMock.getListClient();
const someGuids = Array.from({ length: 13 }).map(() => uuid.v4());
@@ -61,6 +60,13 @@ describe('searchAfterAndBulkCreate', () => {
maxSignals: sampleParams.maxSignals,
buildRuleMessage,
}));
+ bulkCreate = bulkCreateFactory(
+ mockLogger,
+ mockService.scopedClusterClient.asCurrentUser,
+ buildRuleMessage,
+ false
+ );
+ wrapHits = wrapHitsFactory({ ruleSO, signalsIndex: DEFAULT_SIGNALS_INDEX });
});
test('should return success with number of searches less than max signals', async () => {
@@ -166,6 +172,7 @@ describe('searchAfterAndBulkCreate', () => {
},
},
];
+
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
tuples,
ruleSO,
@@ -179,8 +186,9 @@ describe('searchAfterAndBulkCreate', () => {
signalsIndex: DEFAULT_SIGNALS_INDEX,
pageSize: 1,
filter: undefined,
- refresh: false,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
expect(success).toEqual(true);
expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(5);
@@ -282,8 +290,9 @@ describe('searchAfterAndBulkCreate', () => {
signalsIndex: DEFAULT_SIGNALS_INDEX,
pageSize: 1,
filter: undefined,
- refresh: false,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
expect(success).toEqual(true);
expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(4);
@@ -359,8 +368,9 @@ describe('searchAfterAndBulkCreate', () => {
signalsIndex: DEFAULT_SIGNALS_INDEX,
pageSize: 1,
filter: undefined,
- refresh: false,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
expect(success).toEqual(true);
expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(2);
@@ -417,8 +427,9 @@ describe('searchAfterAndBulkCreate', () => {
signalsIndex: DEFAULT_SIGNALS_INDEX,
pageSize: 1,
filter: undefined,
- refresh: false,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
expect(success).toEqual(true);
expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(2);
@@ -495,8 +506,9 @@ describe('searchAfterAndBulkCreate', () => {
signalsIndex: DEFAULT_SIGNALS_INDEX,
pageSize: 1,
filter: undefined,
- refresh: false,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
expect(success).toEqual(true);
expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(2);
@@ -549,8 +561,9 @@ describe('searchAfterAndBulkCreate', () => {
signalsIndex: DEFAULT_SIGNALS_INDEX,
pageSize: 1,
filter: undefined,
- refresh: false,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
expect(success).toEqual(true);
expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
@@ -625,18 +638,14 @@ describe('searchAfterAndBulkCreate', () => {
signalsIndex: DEFAULT_SIGNALS_INDEX,
pageSize: 1,
filter: undefined,
- refresh: false,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
expect(success).toEqual(true);
expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(1);
expect(createdSignalsCount).toEqual(4);
expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000'));
- // I don't like testing log statements since logs change but this is the best
- // way I can think of to ensure this section is getting hit with this test case.
- expect(((mockLogger.debug as unknown) as jest.Mock).mock.calls[14][0]).toContain(
- 'ran out of sort ids to sort on name: "fake name" id: "fake id" rule id: "fake rule id" signals index: "fakeindex"'
- );
});
test('should return success when no exceptions list provided', async () => {
@@ -703,8 +712,9 @@ describe('searchAfterAndBulkCreate', () => {
signalsIndex: DEFAULT_SIGNALS_INDEX,
pageSize: 1,
filter: undefined,
- refresh: false,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
expect(success).toEqual(true);
expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(2);
@@ -746,8 +756,9 @@ describe('searchAfterAndBulkCreate', () => {
signalsIndex: DEFAULT_SIGNALS_INDEX,
pageSize: 1,
filter: undefined,
- refresh: false,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
expect(mockLogger.error).toHaveBeenCalled();
expect(success).toEqual(false);
@@ -792,8 +803,9 @@ describe('searchAfterAndBulkCreate', () => {
signalsIndex: DEFAULT_SIGNALS_INDEX,
pageSize: 1,
filter: undefined,
- refresh: false,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
expect(success).toEqual(true);
expect(createdSignalsCount).toEqual(0);
@@ -852,8 +864,9 @@ describe('searchAfterAndBulkCreate', () => {
signalsIndex: DEFAULT_SIGNALS_INDEX,
pageSize: 1,
filter: undefined,
- refresh: false,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
expect(success).toEqual(false);
expect(createdSignalsCount).toEqual(0); // should not create signals if search threw error
@@ -977,8 +990,9 @@ describe('searchAfterAndBulkCreate', () => {
signalsIndex: DEFAULT_SIGNALS_INDEX,
pageSize: 1,
filter: undefined,
- refresh: false,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
expect(success).toEqual(false);
expect(errors).toEqual(['error on creation']);
@@ -1072,8 +1086,9 @@ describe('searchAfterAndBulkCreate', () => {
signalsIndex: DEFAULT_SIGNALS_INDEX,
pageSize: 1,
filter: undefined,
- refresh: false,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
expect(mockEnrichment).toHaveBeenCalledWith(
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts
index b0dcc1810a639..bb2e57b0606e5 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts
@@ -8,7 +8,6 @@
import { identity } from 'lodash';
import type { estypes } from '@elastic/elasticsearch';
import { singleSearchAfter } from './single_search_after';
-import { singleBulkCreate } from './single_bulk_create';
import { filterEventsAgainstList } from './filters/filter_events_against_list';
import { sendAlertTelemetryEvents } from './send_telemetry_events';
import {
@@ -31,14 +30,13 @@ export const searchAfterAndBulkCreate = async ({
listClient,
logger,
eventsTelemetry,
- id,
inputIndexPattern,
- signalsIndex,
filter,
pageSize,
- refresh,
buildRuleMessage,
enrichment = identity,
+ bulkCreate,
+ wrapHits,
}: SearchAfterAndBulkCreateParams): Promise => {
const ruleParams = ruleSO.attributes.params;
let toReturn = createSearchAfterReturnType();
@@ -149,6 +147,7 @@ export const searchAfterAndBulkCreate = async ({
);
}
const enrichedEvents = await enrichment(filteredEvents);
+ const wrappedDocs = wrapHits(enrichedEvents.hits.hits);
const {
bulkCreateDuration: bulkDuration,
@@ -156,16 +155,7 @@ export const searchAfterAndBulkCreate = async ({
createdItems,
success: bulkSuccess,
errors: bulkErrors,
- } = await singleBulkCreate({
- buildRuleMessage,
- filteredEvents: enrichedEvents,
- ruleSO,
- services,
- logger,
- id,
- signalsIndex,
- refresh,
- });
+ } = await bulkCreate(wrappedDocs);
toReturn = mergeReturns([
toReturn,
createSearchAfterReturnType({
@@ -180,10 +170,10 @@ export const searchAfterAndBulkCreate = async ({
logger.debug(buildRuleMessage(`created ${createdCount} signals`));
logger.debug(buildRuleMessage(`signalsCreatedCount: ${signalsCreatedCount}`));
logger.debug(
- buildRuleMessage(`filteredEvents.hits.hits: ${filteredEvents.hits.hits.length}`)
+ buildRuleMessage(`enrichedEvents.hits.hits: ${enrichedEvents.hits.hits.length}`)
);
- sendAlertTelemetryEvents(logger, eventsTelemetry, filteredEvents, buildRuleMessage);
+ sendAlertTelemetryEvents(logger, eventsTelemetry, enrichedEvents, buildRuleMessage);
}
if (!hasSortId) {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts
index 823d694f36514..d8c919b50e9db 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts
@@ -274,35 +274,6 @@ describe('signal_rule_alert_type', () => {
expect(ruleStatusService.error).toHaveBeenCalledTimes(0);
});
- it("should set refresh to 'wait_for' when actions are present", async () => {
- const ruleAlert = getAlertMock(getQueryRuleParams());
- ruleAlert.actions = [
- {
- actionTypeId: '.slack',
- params: {
- message:
- 'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}',
- },
- group: 'default',
- id: '99403909-ca9b-49ba-9d7a-7e5320e68d05',
- },
- ];
-
- alertServices.savedObjectsClient.get.mockResolvedValue({
- id: 'id',
- type: 'type',
- references: [],
- attributes: ruleAlert,
- });
- await alert.executor(payload);
- expect((queryExecutor as jest.Mock).mock.calls[0][0].refresh).toEqual('wait_for');
- });
-
- it('should set refresh to false when actions are not present', async () => {
- await alert.executor(payload);
- expect((queryExecutor as jest.Mock).mock.calls[0][0].refresh).toEqual(false);
- });
-
it('should call scheduleActions if signalsCount was greater than 0 and rule has actions defined', async () => {
const ruleAlert = getAlertMock(getQueryRuleParams());
ruleAlert.actions = [
@@ -462,6 +433,7 @@ describe('signal_rule_alert_type', () => {
lastLookBackDate: null,
createdSignalsCount: 0,
createdSignals: [],
+ warningMessages: [],
errors: ['Error that bubbled up.'],
};
(queryExecutor as jest.Mock).mockResolvedValue(result);
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
index 13a63df6ed8b6..0a2e22bc44b60 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts
@@ -65,6 +65,8 @@ import {
RuleParams,
savedQueryRuleParams,
} from '../schemas/rule_schemas';
+import { bulkCreateFactory } from './bulk_create_factory';
+import { wrapHitsFactory } from './wrap_hits_factory';
export const signalRulesAlertType = ({
logger,
@@ -218,6 +220,19 @@ export const signalRulesAlertType = ({
client: exceptionsClient,
lists: params.exceptionsList ?? [],
});
+
+ const bulkCreate = bulkCreateFactory(
+ logger,
+ services.scopedClusterClient.asCurrentUser,
+ buildRuleMessage,
+ refresh
+ );
+
+ const wrapHits = wrapHitsFactory({
+ ruleSO: savedObject,
+ signalsIndex: params.outputIndex,
+ });
+
if (isMlRule(type)) {
const mlRuleSO = asTypeSpecificSO(savedObject, machineLearningRuleParams);
result = await mlExecutor({
@@ -225,11 +240,11 @@ export const signalRulesAlertType = ({
ml,
listClient,
exceptionItems,
- ruleStatusService,
services,
logger,
- refresh,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
} else if (isThresholdRule(type)) {
const thresholdRuleSO = asTypeSpecificSO(savedObject, thresholdRuleParams);
@@ -237,13 +252,13 @@ export const signalRulesAlertType = ({
rule: thresholdRuleSO,
tuples,
exceptionItems,
- ruleStatusService,
services,
version,
logger,
- refresh,
buildRuleMessage,
startedAt,
+ bulkCreate,
+ wrapHits,
});
} else if (isThreatMatchRule(type)) {
const threatRuleSO = asTypeSpecificSO(savedObject, threatRuleParams);
@@ -256,9 +271,10 @@ export const signalRulesAlertType = ({
version,
searchAfterSize,
logger,
- refresh,
eventsTelemetry,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
} else if (isQueryRule(type)) {
const queryRuleSO = validateQueryRuleTypes(savedObject);
@@ -271,25 +287,30 @@ export const signalRulesAlertType = ({
version,
searchAfterSize,
logger,
- refresh,
eventsTelemetry,
buildRuleMessage,
+ bulkCreate,
+ wrapHits,
});
} else if (isEqlRule(type)) {
const eqlRuleSO = asTypeSpecificSO(savedObject, eqlRuleParams);
result = await eqlExecutor({
rule: eqlRuleSO,
exceptionItems,
- ruleStatusService,
services,
version,
searchAfterSize,
+ bulkCreate,
logger,
- refresh,
});
} else {
throw new Error(`unknown rule type ${type}`);
}
+ if (result.warningMessages.length) {
+ const warningMessage = buildRuleMessage(result.warningMessages.join());
+ await ruleStatusService.partialFailure(warningMessage);
+ }
+
if (result.success) {
if (actions.length) {
const notificationRuleParams: NotificationRuleTypeParams = {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts
deleted file mode 100644
index 3fbb8c1a607e9..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts
+++ /dev/null
@@ -1,318 +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 { generateId } from './utils';
-import {
- sampleDocSearchResultsNoSortId,
- mockLogger,
- sampleRuleGuid,
- sampleDocSearchResultsNoSortIdNoVersion,
- sampleEmptyDocSearchResults,
- sampleBulkCreateDuplicateResult,
- sampleBulkCreateErrorResult,
- sampleDocWithAncestors,
- sampleRuleSO,
-} from './__mocks__/es_results';
-import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants';
-import { singleBulkCreate, filterDuplicateRules } from './single_bulk_create';
-import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks';
-import { buildRuleMessageFactory } from './rule_messages';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';
-import { getQueryRuleParams } from '../schemas/rule_schemas.mock';
-
-const buildRuleMessage = buildRuleMessageFactory({
- id: 'fake id',
- ruleId: 'fake rule id',
- index: 'fakeindex',
- name: 'fake name',
-});
-describe('singleBulkCreate', () => {
- const mockService: AlertServicesMock = alertsMock.createAlertServices();
-
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- describe('create signal id gereateId', () => {
- test('two docs with same index, id, and version should have same id', () => {
- const findex = 'myfakeindex';
- const fid = 'somefakeid';
- const version = '1';
- const ruleId = 'rule-1';
- // 'myfakeindexsomefakeid1rule-1'
- const generatedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679';
- const firstHash = generateId(findex, fid, version, ruleId);
- const secondHash = generateId(findex, fid, version, ruleId);
- expect(firstHash).toEqual(generatedHash);
- expect(secondHash).toEqual(generatedHash);
- expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field
- expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512);
- });
- test('two docs with different index, id, and version should have different id', () => {
- const findex = 'myfakeindex';
- const findex2 = 'mysecondfakeindex';
- const fid = 'somefakeid';
- const version = '1';
- const ruleId = 'rule-1';
- // 'myfakeindexsomefakeid1rule-1'
- const firstGeneratedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679';
- // 'mysecondfakeindexsomefakeid1rule-1'
- const secondGeneratedHash =
- 'a852941273f805ffe9006e574601acc8ae1148d6c0b3f7f8c4785cba8f6b768a';
- const firstHash = generateId(findex, fid, version, ruleId);
- const secondHash = generateId(findex2, fid, version, ruleId);
- expect(firstHash).toEqual(firstGeneratedHash);
- expect(secondHash).toEqual(secondGeneratedHash);
- expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field
- expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512);
- expect(firstHash).not.toEqual(secondHash);
- });
- test('two docs with same index, different id, and same version should have different id', () => {
- const findex = 'myfakeindex';
- const fid = 'somefakeid';
- const fid2 = 'somefakeid2';
- const version = '1';
- const ruleId = 'rule-1';
- // 'myfakeindexsomefakeid1rule-1'
- const firstGeneratedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679';
- // 'myfakeindexsomefakeid21rule-1'
- const secondGeneratedHash =
- '7d33faea18159fd010c4b79890620e8b12cdc88ec1d370149d0e5552ce860255';
- const firstHash = generateId(findex, fid, version, ruleId);
- const secondHash = generateId(findex, fid2, version, ruleId);
- expect(firstHash).toEqual(firstGeneratedHash);
- expect(secondHash).toEqual(secondGeneratedHash);
- expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field
- expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512);
- expect(firstHash).not.toEqual(secondHash);
- });
- test('two docs with same index, same id, and different version should have different id', () => {
- const findex = 'myfakeindex';
- const fid = 'somefakeid';
- const version = '1';
- const version2 = '2';
- const ruleId = 'rule-1';
- // 'myfakeindexsomefakeid1rule-1'
- const firstGeneratedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679';
- // myfakeindexsomefakeid2rule-1'
- const secondGeneratedHash =
- 'f016f3071fa9df9221d2fb2ba92389d4d388a4347c6ec7a4012c01cb1c640a40';
- const firstHash = generateId(findex, fid, version, ruleId);
- const secondHash = generateId(findex, fid, version2, ruleId);
- expect(firstHash).toEqual(firstGeneratedHash);
- expect(secondHash).toEqual(secondGeneratedHash);
- expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field
- expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512);
- expect(firstHash).not.toEqual(secondHash);
- });
- test('Ensure generated id is less than 512 bytes, even for really really long strings', () => {
- const longIndexName =
- 'myfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindex';
- const fid = 'somefakeid';
- const version = '1';
- const ruleId = 'rule-1';
- const firstHash = generateId(longIndexName, fid, version, ruleId);
- expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field
- });
- test('two docs with same index, same id, same version number, and different rule ids should have different id', () => {
- const findex = 'myfakeindex';
- const fid = 'somefakeid';
- const version = '1';
- const ruleId = 'rule-1';
- const ruleId2 = 'rule-2';
- // 'myfakeindexsomefakeid1rule-1'
- const firstGeneratedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679';
- // myfakeindexsomefakeid1rule-2'
- const secondGeneratedHash =
- '1eb04f997086f8b3b143d4d9b18ac178c4a7423f71a5dad9ba8b9e92603c6863';
- const firstHash = generateId(findex, fid, version, ruleId);
- const secondHash = generateId(findex, fid, version, ruleId2);
- expect(firstHash).toEqual(firstGeneratedHash);
- expect(secondHash).toEqual(secondGeneratedHash);
- expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field
- expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512);
- expect(firstHash).not.toEqual(secondHash);
- });
- });
-
- test('create successful bulk create', async () => {
- const ruleSO = sampleRuleSO(getQueryRuleParams());
- mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce(
- // @ts-expect-error not compatible response interface
- elasticsearchClientMock.createSuccessTransportRequestPromise({
- took: 100,
- errors: false,
- items: [
- {
- fakeItemValue: 'fakeItemKey',
- },
- ],
- })
- );
- const { success, createdItemsCount } = await singleBulkCreate({
- filteredEvents: sampleDocSearchResultsNoSortId(),
- ruleSO,
- services: mockService,
- logger: mockLogger,
- id: sampleRuleGuid,
- signalsIndex: DEFAULT_SIGNALS_INDEX,
- refresh: false,
- buildRuleMessage,
- });
- expect(success).toEqual(true);
- expect(createdItemsCount).toEqual(0);
- });
-
- test('create successful bulk create with docs with no versioning', async () => {
- const ruleSO = sampleRuleSO(getQueryRuleParams());
- mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce(
- // @ts-expect-error not compatible response interface
- elasticsearchClientMock.createSuccessTransportRequestPromise({
- took: 100,
- errors: false,
- items: [
- {
- fakeItemValue: 'fakeItemKey',
- },
- ],
- })
- );
- const { success, createdItemsCount } = await singleBulkCreate({
- filteredEvents: sampleDocSearchResultsNoSortIdNoVersion(),
- ruleSO,
- services: mockService,
- logger: mockLogger,
- id: sampleRuleGuid,
- signalsIndex: DEFAULT_SIGNALS_INDEX,
- refresh: false,
- buildRuleMessage,
- });
- expect(success).toEqual(true);
- expect(createdItemsCount).toEqual(0);
- });
-
- test('create unsuccessful bulk create due to empty search results', async () => {
- const ruleSO = sampleRuleSO(getQueryRuleParams());
- mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValue(
- // @ts-expect-error not full response interface
- elasticsearchClientMock.createSuccessTransportRequestPromise(false)
- );
- const { success, createdItemsCount } = await singleBulkCreate({
- filteredEvents: sampleEmptyDocSearchResults(),
- ruleSO,
- services: mockService,
- logger: mockLogger,
- id: sampleRuleGuid,
- signalsIndex: DEFAULT_SIGNALS_INDEX,
- refresh: false,
- buildRuleMessage,
- });
- expect(success).toEqual(true);
- expect(createdItemsCount).toEqual(0);
- });
-
- test('create successful bulk create when bulk create has duplicate errors', async () => {
- const ruleSO = sampleRuleSO(getQueryRuleParams());
- mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValue(
- elasticsearchClientMock.createSuccessTransportRequestPromise(sampleBulkCreateDuplicateResult)
- );
- const { success, createdItemsCount } = await singleBulkCreate({
- filteredEvents: sampleDocSearchResultsNoSortId(),
- ruleSO,
- services: mockService,
- logger: mockLogger,
- id: sampleRuleGuid,
- signalsIndex: DEFAULT_SIGNALS_INDEX,
- refresh: false,
- buildRuleMessage,
- });
-
- expect(mockLogger.error).not.toHaveBeenCalled();
- expect(success).toEqual(true);
- expect(createdItemsCount).toEqual(1);
- });
-
- test('create failed bulk create when bulk create has multiple error statuses', async () => {
- const ruleSO = sampleRuleSO(getQueryRuleParams());
- mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValue(
- elasticsearchClientMock.createSuccessTransportRequestPromise(sampleBulkCreateErrorResult)
- );
- const { success, createdItemsCount, errors } = await singleBulkCreate({
- filteredEvents: sampleDocSearchResultsNoSortId(),
- ruleSO,
- services: mockService,
- logger: mockLogger,
- id: sampleRuleGuid,
- signalsIndex: DEFAULT_SIGNALS_INDEX,
- refresh: false,
- buildRuleMessage,
- });
- expect(mockLogger.error).toHaveBeenCalled();
- expect(errors).toEqual(['[4]: internal server error']);
- expect(success).toEqual(false);
- expect(createdItemsCount).toEqual(1);
- });
-
- test('filter duplicate rules will return an empty array given an empty array', () => {
- const filtered = filterDuplicateRules(
- '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
- sampleEmptyDocSearchResults()
- );
- expect(filtered).toEqual([]);
- });
-
- test('filter duplicate rules will return nothing filtered when the two rule ids do not match with each other', () => {
- const filtered = filterDuplicateRules('some id', sampleDocWithAncestors());
- expect(filtered).toEqual(sampleDocWithAncestors().hits.hits);
- });
-
- test('filters duplicate rules will return empty array when the two rule ids match each other', () => {
- const filtered = filterDuplicateRules(
- '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
- sampleDocWithAncestors()
- );
- expect(filtered).toEqual([]);
- });
-
- test('filter duplicate rules will return back search responses if they do not have a signal and will NOT filter the source out', () => {
- const ancestors = sampleDocSearchResultsNoSortId();
- const filtered = filterDuplicateRules('04128c15-0d1b-4716-a4c5-46997ac7f3bd', ancestors);
- expect(filtered).toEqual(ancestors.hits.hits);
- });
-
- test('filter duplicate rules does not attempt filters when the signal is not an event type of signal but rather a "clash" from the source index having its own numeric signal type', () => {
- const doc = { ...sampleDocWithAncestors(), _source: { signal: 1234 } };
- const filtered = filterDuplicateRules('04128c15-0d1b-4716-a4c5-46997ac7f3bd', doc);
- expect(filtered).toEqual([]);
- });
-
- test('filter duplicate rules does not attempt filters when the signal is not an event type of signal but rather a "clash" from the source index having its own object signal type', () => {
- const doc = { ...sampleDocWithAncestors(), _source: { signal: {} } };
- const filtered = filterDuplicateRules('04128c15-0d1b-4716-a4c5-46997ac7f3bd', doc);
- expect(filtered).toEqual([]);
- });
-
- test('create successful and returns proper createdItemsCount', async () => {
- const ruleSO = sampleRuleSO(getQueryRuleParams());
- mockService.scopedClusterClient.asCurrentUser.bulk.mockResolvedValueOnce(
- elasticsearchClientMock.createSuccessTransportRequestPromise(sampleBulkCreateDuplicateResult)
- );
- const { success, createdItemsCount } = await singleBulkCreate({
- filteredEvents: sampleDocSearchResultsNoSortId(),
- ruleSO,
- services: mockService,
- logger: mockLogger,
- id: sampleRuleGuid,
- signalsIndex: DEFAULT_SIGNALS_INDEX,
- refresh: false,
- buildRuleMessage,
- });
- expect(success).toEqual(true);
- expect(createdItemsCount).toEqual(1);
- });
-});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts
deleted file mode 100644
index 92d01fef6e50c..0000000000000
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts
+++ /dev/null
@@ -1,227 +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 { countBy, isEmpty, get } from 'lodash';
-import { performance } from 'perf_hooks';
-import {
- AlertInstanceContext,
- AlertInstanceState,
- AlertServices,
-} from '../../../../../alerting/server';
-import { AlertAttributes, SignalHit, SignalSearchResponse, WrappedSignalHit } from './types';
-import { RefreshTypes } from '../types';
-import { generateId, makeFloatString, errorAggregator } from './utils';
-import { buildBulkBody } from './build_bulk_body';
-import { BuildRuleMessage } from './rule_messages';
-import { Logger, SavedObject } from '../../../../../../../src/core/server';
-import { isEventTypeSignal } from './build_event_type_signal';
-
-interface SingleBulkCreateParams {
- filteredEvents: SignalSearchResponse;
- ruleSO: SavedObject;
- services: AlertServices;
- logger: Logger;
- id: string;
- signalsIndex: string;
- refresh: RefreshTypes;
- buildRuleMessage: BuildRuleMessage;
-}
-
-/**
- * This is for signals on signals to work correctly. If given a rule id this will check if
- * that rule id already exists in the ancestor tree of each signal search response and remove
- * those documents so they cannot be created as a signal since we do not want a rule id to
- * ever be capable of re-writing the same signal continuously if both the _input_ and _output_
- * of the signals index happens to be the same index.
- * @param ruleId The rule id
- * @param signalSearchResponse The search response that has all the documents
- */
-export const filterDuplicateRules = (
- ruleId: string,
- signalSearchResponse: SignalSearchResponse
-) => {
- return signalSearchResponse.hits.hits.filter((doc) => {
- if (doc._source?.signal == null || !isEventTypeSignal(doc)) {
- return true;
- } else {
- return !(
- doc._source?.signal.ancestors.some((ancestor) => ancestor.rule === ruleId) ||
- doc._source?.signal.rule.id === ruleId
- );
- }
- });
-};
-
-/**
- * Similar to filterDuplicateRules, but operates on candidate signal documents rather than events that matched
- * the detection query. This means we only have to compare the ruleId against the ancestors array.
- * @param ruleId The rule id
- * @param signals The candidate new signals
- */
-export const filterDuplicateSignals = (ruleId: string, signals: WrappedSignalHit[]) => {
- return signals.filter(
- (doc) => !doc._source.signal?.ancestors.some((ancestor) => ancestor.rule === ruleId)
- );
-};
-
-export interface SingleBulkCreateResponse {
- success: boolean;
- bulkCreateDuration?: string;
- createdItemsCount: number;
- createdItems: SignalHit[];
- errors: string[];
-}
-
-export interface BulkInsertSignalsResponse {
- bulkCreateDuration: string;
- createdItemsCount: number;
- createdItems: SignalHit[];
-}
-
-// Bulk Index documents.
-export const singleBulkCreate = async ({
- buildRuleMessage,
- filteredEvents,
- ruleSO,
- services,
- logger,
- id,
- signalsIndex,
- refresh,
-}: SingleBulkCreateParams): Promise => {
- const ruleParams = ruleSO.attributes.params;
- filteredEvents.hits.hits = filterDuplicateRules(id, filteredEvents);
- logger.debug(buildRuleMessage(`about to bulk create ${filteredEvents.hits.hits.length} events`));
- if (filteredEvents.hits.hits.length === 0) {
- logger.debug(buildRuleMessage(`all events were duplicates`));
- return { success: true, createdItemsCount: 0, createdItems: [], errors: [] };
- }
- // index documents after creating an ID based on the
- // source documents' originating index, and the original
- // document _id. This will allow two documents from two
- // different indexes with the same ID to be
- // indexed, and prevents us from creating any updates
- // to the documents once inserted into the signals index,
- // while preventing duplicates from being added to the
- // signals index if rules are re-run over the same time
- // span. Also allow for versioning.
- const bulkBody = filteredEvents.hits.hits.flatMap((doc) => [
- {
- create: {
- _index: signalsIndex,
- _id: generateId(
- doc._index,
- doc._id,
- doc._version ? doc._version.toString() : '',
- ruleParams.ruleId ?? ''
- ),
- },
- },
- buildBulkBody(ruleSO, doc),
- ]);
- const start = performance.now();
- const { body: response } = await services.scopedClusterClient.asCurrentUser.bulk({
- index: signalsIndex,
- refresh,
- body: bulkBody,
- });
- const end = performance.now();
- logger.debug(
- buildRuleMessage(
- `individual bulk process time took: ${makeFloatString(end - start)} milliseconds`
- )
- );
- logger.debug(buildRuleMessage(`took property says bulk took: ${response.took} milliseconds`));
- const createdItems = filteredEvents.hits.hits
- .map((doc, index) => ({
- _id: response.items[index].create?._id ?? '',
- _index: response.items[index].create?._index ?? '',
- ...buildBulkBody(ruleSO, doc),
- }))
- .filter((_, index) => get(response.items[index], 'create.status') === 201);
- const createdItemsCount = createdItems.length;
- const duplicateSignalsCount = countBy(response.items, 'create.status')['409'];
- const errorCountByMessage = errorAggregator(response, [409]);
-
- logger.debug(buildRuleMessage(`bulk created ${createdItemsCount} signals`));
- if (duplicateSignalsCount > 0) {
- logger.debug(buildRuleMessage(`ignored ${duplicateSignalsCount} duplicate signals`));
- }
-
- if (!isEmpty(errorCountByMessage)) {
- logger.error(
- buildRuleMessage(
- `[-] bulkResponse had errors with responses of: ${JSON.stringify(errorCountByMessage)}`
- )
- );
- return {
- errors: Object.keys(errorCountByMessage),
- success: false,
- bulkCreateDuration: makeFloatString(end - start),
- createdItemsCount,
- createdItems,
- };
- } else {
- return {
- errors: [],
- success: true,
- bulkCreateDuration: makeFloatString(end - start),
- createdItemsCount,
- createdItems,
- };
- }
-};
-
-// Bulk Index new signals.
-export const bulkInsertSignals = async (
- signals: WrappedSignalHit[],
- logger: Logger,
- services: AlertServices,
- refresh: RefreshTypes
-): Promise => {
- // index documents after creating an ID based on the
- // id and index of each parent and the rule ID
- const bulkBody = signals.flatMap((doc) => [
- {
- create: {
- _index: doc._index,
- _id: doc._id,
- },
- },
- doc._source,
- ]);
- const start = performance.now();
- const { body: response } = await services.scopedClusterClient.asCurrentUser.bulk({
- refresh,
- body: bulkBody,
- });
- const end = performance.now();
- logger.debug(`individual bulk process time took: ${makeFloatString(end - start)} milliseconds`);
- logger.debug(`took property says bulk took: ${response.took} milliseconds`);
-
- if (response.errors) {
- const duplicateSignalsCount = countBy(response.items, 'create.status')['409'];
- logger.debug(`ignored ${duplicateSignalsCount} duplicate signals`);
- const errorCountByMessage = errorAggregator(response, [409]);
- if (!isEmpty(errorCountByMessage)) {
- logger.error(
- `[-] bulkResponse had errors with responses of: ${JSON.stringify(errorCountByMessage)}`
- );
- }
- }
-
- const createdItemsCount = countBy(response.items, 'create.status')['201'] ?? 0;
- const createdItems = signals
- .map((doc, index) => ({
- ...doc._source,
- _id: response.items[index].create?._id ?? '',
- _index: response.items[index].create?._index ?? '',
- }))
- .filter((_, index) => get(response.items[index], 'create.status') === 201);
- logger.debug(`bulk created ${createdItemsCount} signals`);
- return { bulkCreateDuration: makeFloatString(end - start), createdItems, createdItemsCount };
-};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts
index 37b0b88d88eda..3e30a08f1ae69 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts
@@ -31,10 +31,11 @@ export const createThreatSignal = async ({
outputIndex,
ruleSO,
searchAfterSize,
- refresh,
buildRuleMessage,
currentThreatList,
currentResult,
+ bulkCreate,
+ wrapHits,
}: CreateThreatSignalOptions): Promise => {
const threatFilter = buildThreatMappingFilter({
threatMapping,
@@ -81,9 +82,10 @@ export const createThreatSignal = async ({
signalsIndex: outputIndex,
filter: esFilter,
pageSize: searchAfterSize,
- refresh,
buildRuleMessage,
enrichment: threatEnrichment,
+ bulkCreate,
+ wrapHits,
});
logger.debug(
buildRuleMessage(
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts
index b3e0e376c7794..5054ab1b2cca5 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts
@@ -32,7 +32,6 @@ export const createThreatSignals = async ({
outputIndex,
ruleSO,
searchAfterSize,
- refresh,
threatFilters,
threatQuery,
threatLanguage,
@@ -41,6 +40,8 @@ export const createThreatSignals = async ({
threatIndicatorPath,
concurrentSearches,
itemsPerSearch,
+ bulkCreate,
+ wrapHits,
}: CreateThreatSignalsOptions): Promise => {
const params = ruleSO.attributes.params;
logger.debug(buildRuleMessage('Indicator matching rule starting'));
@@ -55,6 +56,7 @@ export const createThreatSignals = async ({
createdSignalsCount: 0,
createdSignals: [],
errors: [],
+ warningMessages: [],
};
let threatListCount = await getThreatListCount({
@@ -120,10 +122,11 @@ export const createThreatSignals = async ({
outputIndex,
ruleSO,
searchAfterSize,
- refresh,
buildRuleMessage,
currentThreatList: slicedChunk,
currentResult: results,
+ bulkCreate,
+ wrapHits,
})
);
const searchesPerformed = await Promise.all(concurrentSearchesPerformed);
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts
index acb64f826f3f2..34b064b0f8805 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts
@@ -29,9 +29,11 @@ import { TelemetryEventsSender } from '../../../telemetry/sender';
import { BuildRuleMessage } from '../rule_messages';
import {
AlertAttributes,
+ BulkCreate,
RuleRangeTuple,
SearchAfterAndBulkCreateReturnType,
SignalsEnrichment,
+ WrapHits,
} from '../types';
import { ThreatRuleParams } from '../../schemas/rule_schemas';
@@ -55,7 +57,6 @@ export interface CreateThreatSignalsOptions {
outputIndex: string;
ruleSO: SavedObject>;
searchAfterSize: number;
- refresh: false | 'wait_for';
threatFilters: unknown[];
threatQuery: ThreatQuery;
buildRuleMessage: BuildRuleMessage;
@@ -64,6 +65,8 @@ export interface CreateThreatSignalsOptions {
threatLanguage: ThreatLanguageOrUndefined;
concurrentSearches: ConcurrentSearches;
itemsPerSearch: ItemsPerSearch;
+ bulkCreate: BulkCreate;
+ wrapHits: WrapHits;
}
export interface CreateThreatSignalOptions {
@@ -85,10 +88,11 @@ export interface CreateThreatSignalOptions {
outputIndex: string;
ruleSO: SavedObject>;
searchAfterSize: number;
- refresh: false | 'wait_for';
buildRuleMessage: BuildRuleMessage;
currentThreatList: ThreatListItem[];
currentResult: SearchAfterAndBulkCreateReturnType;
+ bulkCreate: BulkCreate;
+ wrapHits: WrapHits;
}
export interface BuildThreatMappingFilterOptions {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts
index 6c6447bad0975..ec826b44023f6 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts
@@ -58,6 +58,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult: SearchAfterAndBulkCreateReturnType = {
@@ -69,6 +70,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const combinedResults = combineResults(existingResult, newResult);
expect(combinedResults.success).toEqual(true);
@@ -84,6 +86,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult: SearchAfterAndBulkCreateReturnType = {
@@ -95,6 +98,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const combinedResults = combineResults(existingResult, newResult);
expect(combinedResults.success).toEqual(false);
@@ -110,6 +114,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult: SearchAfterAndBulkCreateReturnType = {
@@ -121,6 +126,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const combinedResults = combineResults(existingResult, newResult);
expect(combinedResults.lastLookBackDate?.toISOString()).toEqual('2020-09-16T03:34:32.390Z');
@@ -136,6 +142,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult: SearchAfterAndBulkCreateReturnType = {
@@ -147,6 +154,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const combinedResults = combineResults(existingResult, newResult);
expect(combinedResults).toEqual(
@@ -167,6 +175,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: ['error 1', 'error 2', 'error 3'],
+ warningMessages: [],
};
const newResult: SearchAfterAndBulkCreateReturnType = {
@@ -178,6 +187,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: ['error 4', 'error 1', 'error 3', 'error 5'],
+ warningMessages: [],
};
const combinedResults = combineResults(existingResult, newResult);
expect(combinedResults).toEqual(
@@ -289,6 +299,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const expectedResult: SearchAfterAndBulkCreateReturnType = {
success: true,
@@ -299,6 +310,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const combinedResults = combineConcurrentResults(existingResult, []);
expect(combinedResults).toEqual(expectedResult);
@@ -314,6 +326,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult: SearchAfterAndBulkCreateReturnType = {
success: true,
@@ -324,6 +337,7 @@ describe('utils', () => {
createdSignalsCount: 0,
createdSignals: [],
errors: [],
+ warningMessages: [],
};
const expectedResult: SearchAfterAndBulkCreateReturnType = {
success: true,
@@ -334,6 +348,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const combinedResults = combineConcurrentResults(existingResult, [newResult]);
@@ -350,6 +365,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult1: SearchAfterAndBulkCreateReturnType = {
success: true,
@@ -360,6 +376,7 @@ describe('utils', () => {
createdSignalsCount: 5,
createdSignals: Array(5).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult2: SearchAfterAndBulkCreateReturnType = {
success: true,
@@ -370,6 +387,7 @@ describe('utils', () => {
createdSignalsCount: 8,
createdSignals: Array(8).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const expectedResult: SearchAfterAndBulkCreateReturnType = {
@@ -381,6 +399,7 @@ describe('utils', () => {
createdSignalsCount: 16, // all the signals counted together (8 + 5 + 3)
createdSignals: Array(16).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const combinedResults = combineConcurrentResults(existingResult, [newResult1, newResult2]);
@@ -397,6 +416,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult1: SearchAfterAndBulkCreateReturnType = {
success: true,
@@ -407,6 +427,7 @@ describe('utils', () => {
createdSignalsCount: 5,
createdSignals: Array(5).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult2: SearchAfterAndBulkCreateReturnType = {
success: true,
@@ -417,6 +438,7 @@ describe('utils', () => {
createdSignalsCount: 8,
createdSignals: Array(8).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const expectedResult: SearchAfterAndBulkCreateReturnType = {
@@ -428,6 +450,7 @@ describe('utils', () => {
createdSignalsCount: 16, // all the signals counted together (8 + 5 + 3)
createdSignals: Array(16).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const combinedResults = combineConcurrentResults(existingResult, [newResult2, newResult1]); // two array elements are flipped
@@ -444,6 +467,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult1: SearchAfterAndBulkCreateReturnType = {
success: true,
@@ -454,6 +478,7 @@ describe('utils', () => {
createdSignalsCount: 5,
createdSignals: Array(5).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult2: SearchAfterAndBulkCreateReturnType = {
success: true,
@@ -464,6 +489,7 @@ describe('utils', () => {
createdSignalsCount: 8,
createdSignals: Array(8).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const expectedResult: SearchAfterAndBulkCreateReturnType = {
@@ -475,6 +501,7 @@ describe('utils', () => {
createdSignalsCount: 16, // all the signals counted together (8 + 5 + 3)
createdSignals: Array(16).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const combinedResults = combineConcurrentResults(existingResult, [newResult1, newResult2]);
@@ -491,6 +518,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult: SearchAfterAndBulkCreateReturnType = {
@@ -502,6 +530,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const combinedResults = combineConcurrentResults(existingResult, [newResult]);
expect(combinedResults.success).toEqual(true);
@@ -517,6 +546,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult: SearchAfterAndBulkCreateReturnType = {
@@ -528,6 +558,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const combinedResults = combineConcurrentResults(existingResult, [newResult]);
expect(combinedResults.success).toEqual(false);
@@ -543,6 +574,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult: SearchAfterAndBulkCreateReturnType = {
@@ -554,6 +586,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const combinedResults = combineConcurrentResults(existingResult, [newResult]);
expect(combinedResults.lastLookBackDate?.toISOString()).toEqual('2020-09-16T03:34:32.390Z');
@@ -569,6 +602,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const newResult: SearchAfterAndBulkCreateReturnType = {
@@ -580,6 +614,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: [],
+ warningMessages: [],
};
const combinedResults = combineConcurrentResults(existingResult, [newResult]);
expect(combinedResults).toEqual(
@@ -600,6 +635,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: ['error 1', 'error 2', 'error 3'],
+ warningMessages: [],
};
const newResult: SearchAfterAndBulkCreateReturnType = {
@@ -611,6 +647,7 @@ describe('utils', () => {
createdSignalsCount: 3,
createdSignals: Array(3).fill(sampleSignalHit()),
errors: ['error 4', 'error 1', 'error 3', 'error 5'],
+ warningMessages: [],
};
const combinedResults = combineConcurrentResults(existingResult, [newResult]);
expect(combinedResults).toEqual(
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts
index 47a32915dd83f..4d9fda43f032e 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts
@@ -75,6 +75,7 @@ export const combineResults = (
lastLookBackDate: newResult.lastLookBackDate,
createdSignalsCount: currentResult.createdSignalsCount + newResult.createdSignalsCount,
createdSignals: [...currentResult.createdSignals, ...newResult.createdSignals],
+ warningMessages: [...currentResult.warningMessages, ...newResult.warningMessages],
errors: [...new Set([...currentResult.errors, ...newResult.errors])],
});
@@ -100,6 +101,7 @@ export const combineConcurrentResults = (
lastLookBackDate,
createdSignalsCount: accum.createdSignalsCount + item.createdSignalsCount,
createdSignals: [...accum.createdSignals, ...item.createdSignals],
+ warningMessages: [...accum.warningMessages, ...item.warningMessages],
errors: [...new Set([...accum.errors, ...item.errors])],
};
},
@@ -112,6 +114,7 @@ export const combineConcurrentResults = (
createdSignalsCount: 0,
createdSignals: [],
errors: [],
+ warningMessages: [],
}
);
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts
index 197065f205fc5..08fa2f14a0fd5 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts
@@ -19,20 +19,20 @@ import {
} from '../../../../../../alerting/server';
import { BaseHit } from '../../../../../common/detection_engine/types';
import { TermAggregationBucket } from '../../../types';
-import { RefreshTypes } from '../../types';
-import { singleBulkCreate, SingleBulkCreateResponse } from '../single_bulk_create';
+import { GenericBulkCreateResponse } from '../bulk_create_factory';
import {
calculateThresholdSignalUuid,
getThresholdAggregationParts,
getThresholdTermsHash,
} from '../utils';
-import { BuildRuleMessage } from '../rule_messages';
import type {
MultiAggBucket,
SignalSource,
SignalSearchResponse,
ThresholdSignalHistory,
AlertAttributes,
+ BulkCreate,
+ WrapHits,
} from '../types';
import { ThresholdRuleParams } from '../../schemas/rule_schemas';
@@ -42,14 +42,13 @@ interface BulkCreateThresholdSignalsParams {
services: AlertServices;
inputIndexPattern: string[];
logger: Logger;
- id: string;
filter: unknown;
signalsIndex: string;
- refresh: RefreshTypes;
startedAt: Date;
from: Date;
thresholdSignalHistory: ThresholdSignalHistory;
- buildRuleMessage: BuildRuleMessage;
+ bulkCreate: BulkCreate;
+ wrapHits: WrapHits;
}
const getTransformedHits = (
@@ -76,7 +75,7 @@ const getTransformedHits = (
return [];
}
- const getCombinations = (buckets: TermAggregationBucket[], i: number, field: string) => {
+ const getCombinations = (buckets: TermAggregationBucket[], i: number, field: string | null) => {
return buckets.reduce((acc: MultiAggBucket[], bucket: TermAggregationBucket) => {
if (i < threshold.field.length - 1) {
const nextLevelIdx = i + 1;
@@ -100,7 +99,7 @@ const getTransformedHits = (
topThresholdHits: val.topThresholdHits,
docCount: val.docCount,
};
- acc.push(el);
+ acc.push(el as MultiAggBucket);
});
} else {
const el = {
@@ -121,80 +120,76 @@ const getTransformedHits = (
topThresholdHits: bucket.top_threshold_hits,
docCount: bucket.doc_count,
};
- acc.push(el);
+ acc.push(el as MultiAggBucket);
}
return acc;
}, []);
};
- // Recurse through the nested buckets and collect each unique combination of terms. Collect the
- // cardinality and document count from the leaf buckets and return a signal for each set of terms.
- // @ts-expect-error @elastic/elasticsearch no way to declare a type for aggregation in the search response
- return getCombinations(results.aggregations![aggParts.name].buckets, 0, aggParts.field).reduce(
- (acc: Array>, bucket) => {
- const hit = bucket.topThresholdHits?.hits.hits[0];
- if (hit == null) {
- return acc;
- }
-
- const timestampArray = get(timestampOverride ?? '@timestamp', hit.fields);
- if (timestampArray == null) {
- return acc;
- }
-
- const timestamp = timestampArray[0];
- if (typeof timestamp !== 'string') {
- return acc;
- }
-
- const termsHash = getThresholdTermsHash(bucket.terms);
- const signalHit = thresholdSignalHistory[termsHash];
-
- const source = {
- '@timestamp': timestamp,
- ...bucket.terms.reduce((termAcc, term) => {
- if (!term.field.startsWith('signal.')) {
- return {
- ...termAcc,
- [term.field]: term.value,
- };
- }
- return termAcc;
- }, {}),
- threshold_result: {
- terms: bucket.terms,
- cardinality: bucket.cardinality,
- count: bucket.docCount,
- // Store `from` in the signal so that we know the lower bound for the
- // threshold set in the timeline search. The upper bound will always be
- // the `original_time` of the signal (the timestamp of the latest event
- // in the set).
- from:
- signalHit?.lastSignalTimestamp != null
- ? new Date(signalHit!.lastSignalTimestamp)
- : from,
- },
- };
+ return getCombinations(
+ (results.aggregations![aggParts.name] as { buckets: TermAggregationBucket[] }).buckets,
+ 0,
+ aggParts.field
+ ).reduce((acc: Array>, bucket) => {
+ const hit = bucket.topThresholdHits?.hits.hits[0];
+ if (hit == null) {
+ return acc;
+ }
- acc.push({
- _index: inputIndex,
- _id: calculateThresholdSignalUuid(
- ruleId,
- startedAt,
- threshold.field,
- bucket.terms
- .map((term) => term.value)
- .sort()
- .join(',')
- ),
- _source: source,
- });
+ const timestampArray = get(timestampOverride ?? '@timestamp', hit.fields);
+ if (timestampArray == null) {
+ return acc;
+ }
+ const timestamp = timestampArray[0];
+ if (typeof timestamp !== 'string') {
return acc;
- },
- []
- );
+ }
+
+ const termsHash = getThresholdTermsHash(bucket.terms);
+ const signalHit = thresholdSignalHistory[termsHash];
+
+ const source = {
+ '@timestamp': timestamp,
+ ...bucket.terms.reduce((termAcc, term) => {
+ if (!term.field.startsWith('signal.')) {
+ return {
+ ...termAcc,
+ [term.field]: term.value,
+ };
+ }
+ return termAcc;
+ }, {}),
+ threshold_result: {
+ terms: bucket.terms,
+ cardinality: bucket.cardinality,
+ count: bucket.docCount,
+ // Store `from` in the signal so that we know the lower bound for the
+ // threshold set in the timeline search. The upper bound will always be
+ // the `original_time` of the signal (the timestamp of the latest event
+ // in the set).
+ from:
+ signalHit?.lastSignalTimestamp != null ? new Date(signalHit!.lastSignalTimestamp) : from,
+ },
+ };
+
+ acc.push({
+ _index: inputIndex,
+ _id: calculateThresholdSignalUuid(
+ ruleId,
+ startedAt,
+ threshold.field,
+ bucket.terms
+ .map((term) => term.value)
+ .sort()
+ .join(',')
+ ),
+ _source: source,
+ });
+
+ return acc;
+ }, []);
};
export const transformThresholdResultsToEcs = (
@@ -238,7 +233,7 @@ export const transformThresholdResultsToEcs = (
export const bulkCreateThresholdSignals = async (
params: BulkCreateThresholdSignalsParams
-): Promise => {
+): Promise> => {
const ruleParams = params.ruleSO.attributes.params;
const thresholdResults = params.someResult;
const ecsResults = transformThresholdResultsToEcs(
@@ -253,7 +248,6 @@ export const bulkCreateThresholdSignals = async (
ruleParams.timestampOverride,
params.thresholdSignalHistory
);
- const buildRuleMessage = params.buildRuleMessage;
- return singleBulkCreate({ ...params, filteredEvents: ecsResults, buildRuleMessage });
+ return params.bulkCreate(params.wrapHits(ecsResults.hits.hits));
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts
index 4205c2d6d8b2c..c35eb04ba1270 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts
@@ -26,12 +26,12 @@ import {
RuleAlertAction,
SearchTypes,
} from '../../../../common/detection_engine/types';
-import { RefreshTypes } from '../types';
import { ListClient } from '../../../../../lists/server';
import { Logger, SavedObject } from '../../../../../../../src/core/server';
import { BuildRuleMessage } from './rule_messages';
import { TelemetryEventsSender } from '../../telemetry/sender';
import { RuleParams } from '../schemas/rule_schemas';
+import { GenericBulkCreateResponse } from './bulk_create_factory';
// used for gap detection code
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -255,6 +255,12 @@ export interface QueryFilter {
export type SignalsEnrichment = (signals: SignalSearchResponse) => Promise;
+export type BulkCreate = (docs: Array>) => Promise>;
+
+export type WrapHits = (
+ hits: Array>
+) => Array>;
+
export interface SearchAfterAndBulkCreateParams {
tuples: Array<{
to: moment.Moment;
@@ -272,9 +278,10 @@ export interface SearchAfterAndBulkCreateParams {
signalsIndex: string;
pageSize: number;
filter: unknown;
- refresh: RefreshTypes;
buildRuleMessage: BuildRuleMessage;
enrichment?: SignalsEnrichment;
+ bulkCreate: BulkCreate;
+ wrapHits: WrapHits;
}
export interface SearchAfterAndBulkCreateReturnType {
@@ -284,8 +291,9 @@ export interface SearchAfterAndBulkCreateReturnType {
bulkCreateTimes: string[];
lastLookBackDate: Date | null | undefined;
createdSignalsCount: number;
- createdSignals: SignalHit[];
+ createdSignals: unknown[];
errors: string[];
+ warningMessages: string[];
totalToFromTuples?: Array<{
to: Moment | undefined;
from: Moment | undefined;
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts
index 60bf0ec337f3d..616cf714d6a8c 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts
@@ -1083,6 +1083,7 @@ describe('utils', () => {
searchAfterTimes: [],
success: true,
warning: false,
+ warningMessages: [],
};
expect(newSearchResult).toEqual(expected);
});
@@ -1102,6 +1103,7 @@ describe('utils', () => {
searchAfterTimes: [],
success: true,
warning: false,
+ warningMessages: [],
};
expect(newSearchResult).toEqual(expected);
});
@@ -1380,6 +1382,7 @@ describe('utils', () => {
searchAfterTimes: [],
success: true,
warning: false,
+ warningMessages: [],
};
expect(searchAfterReturnType).toEqual(expected);
});
@@ -1394,6 +1397,7 @@ describe('utils', () => {
searchAfterTimes: ['123'],
success: false,
warning: true,
+ warningMessages: ['test warning'],
});
const expected: SearchAfterAndBulkCreateReturnType = {
bulkCreateTimes: ['123'],
@@ -1404,6 +1408,7 @@ describe('utils', () => {
searchAfterTimes: ['123'],
success: false,
warning: true,
+ warningMessages: ['test warning'],
};
expect(searchAfterReturnType).toEqual(expected);
});
@@ -1423,6 +1428,7 @@ describe('utils', () => {
searchAfterTimes: [],
success: true,
warning: false,
+ warningMessages: [],
};
expect(searchAfterReturnType).toEqual(expected);
});
@@ -1440,6 +1446,7 @@ describe('utils', () => {
searchAfterTimes: [],
success: true,
warning: false,
+ warningMessages: [],
};
expect(merged).toEqual(expected);
});
@@ -1494,6 +1501,7 @@ describe('utils', () => {
lastLookBackDate: new Date('2020-08-21T18:51:25.193Z'),
searchAfterTimes: ['123'],
success: true,
+ warningMessages: ['warning1'],
}),
createSearchAfterReturnType({
bulkCreateTimes: ['456'],
@@ -1503,6 +1511,8 @@ describe('utils', () => {
lastLookBackDate: new Date('2020-09-21T18:51:25.193Z'),
searchAfterTimes: ['567'],
success: true,
+ warningMessages: ['warning2'],
+ warning: true,
}),
]);
const expected: SearchAfterAndBulkCreateReturnType = {
@@ -1513,7 +1523,8 @@ describe('utils', () => {
lastLookBackDate: new Date('2020-09-21T18:51:25.193Z'), // takes the next lastLookBackDate
searchAfterTimes: ['123', '567'], // concatenates the searchAfterTimes together
success: true, // Defaults to success true is all of it was successful
- warning: false,
+ warning: true,
+ warningMessages: ['warning1', 'warning2'],
};
expect(merged).toEqual(expected);
});
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts
index 1de76f64fabec..6d67bab6eb2f7 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts
@@ -4,7 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-
import { createHash } from 'crypto';
import moment from 'moment';
import uuidv5 from 'uuid/v5';
@@ -17,6 +16,7 @@ import type { ListArray, ExceptionListItemSchema } from '@kbn/securitysolution-i
import { MAX_EXCEPTION_LIST_SIZE } from '@kbn/securitysolution-list-constants';
import { hasLargeValueList } from '@kbn/securitysolution-list-utils';
import { parseScheduleDates } from '@kbn/securitysolution-io-ts-utils';
+import { ElasticsearchClient } from '@kbn/securitysolution-es-utils';
import {
TimestampOverrideOrUndefined,
Privilege,
@@ -38,6 +38,7 @@ import {
WrappedSignalHit,
RuleRangeTuple,
BaseSignalHit,
+ SignalSourceHit,
} from './types';
import { BuildRuleMessage } from './rule_messages';
import { ShardError } from '../../types';
@@ -163,9 +164,15 @@ export const hasTimestampFields = async (
export const checkPrivileges = async (
services: AlertServices,
indices: string[]
+): Promise =>
+ checkPrivilegesFromEsClient(services.scopedClusterClient.asCurrentUser, indices);
+
+export const checkPrivilegesFromEsClient = async (
+ esClient: ElasticsearchClient,
+ indices: string[]
): Promise =>
(
- await services.scopedClusterClient.asCurrentUser.transport.request({
+ await esClient.transport.request({
path: '/_security/user/_has_privileges',
method: 'POST',
body: {
@@ -608,7 +615,7 @@ export const getValidDateFromDoc = ({
doc.fields != null && doc.fields[timestamp] != null
? doc.fields[timestamp][0]
: doc._source != null
- ? doc._source[timestamp]
+ ? (doc._source as { [key: string]: unknown })[timestamp]
: undefined;
const lastTimestamp =
typeof timestampValue === 'string' || typeof timestampValue === 'number'
@@ -657,6 +664,7 @@ export const createSearchAfterReturnType = ({
createdSignalsCount,
createdSignals,
errors,
+ warningMessages,
}: {
success?: boolean | undefined;
warning?: boolean;
@@ -664,8 +672,9 @@ export const createSearchAfterReturnType = ({
bulkCreateTimes?: string[] | undefined;
lastLookBackDate?: Date | undefined;
createdSignalsCount?: number | undefined;
- createdSignals?: SignalHit[] | undefined;
+ createdSignals?: unknown[] | undefined;
errors?: string[] | undefined;
+ warningMessages?: string[] | undefined;
} = {}): SearchAfterAndBulkCreateReturnType => {
return {
success: success ?? true,
@@ -676,10 +685,12 @@ export const createSearchAfterReturnType = ({
createdSignalsCount: createdSignalsCount ?? 0,
createdSignals: createdSignals ?? [],
errors: errors ?? [],
+ warningMessages: warningMessages ?? [],
};
};
export const createSearchResultReturnType = (): SignalSearchResponse => {
+ const hits: SignalSourceHit[] = [];
return {
took: 0,
timed_out: false,
@@ -693,7 +704,7 @@ export const createSearchResultReturnType = (): SignalSearchResponse => {
hits: {
total: 0,
max_score: 0,
- hits: [],
+ hits,
},
};
};
@@ -711,7 +722,8 @@ export const mergeReturns = (
createdSignalsCount: existingCreatedSignalsCount,
createdSignals: existingCreatedSignals,
errors: existingErrors,
- } = prev;
+ warningMessages: existingWarningMessages,
+ }: SearchAfterAndBulkCreateReturnType = prev;
const {
success: newSuccess,
@@ -722,7 +734,8 @@ export const mergeReturns = (
createdSignalsCount: newCreatedSignalsCount,
createdSignals: newCreatedSignals,
errors: newErrors,
- } = next;
+ warningMessages: newWarningMessages,
+ }: SearchAfterAndBulkCreateReturnType = next;
return {
success: existingSuccess && newSuccess,
@@ -733,6 +746,7 @@ export const mergeReturns = (
createdSignalsCount: existingCreatedSignalsCount + newCreatedSignalsCount,
createdSignals: [...existingCreatedSignals, ...newCreatedSignals],
errors: [...new Set([...existingErrors, ...newErrors])],
+ warningMessages: [...existingWarningMessages, ...newWarningMessages],
};
});
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts
new file mode 100644
index 0000000000000..3f3e4ef3631bd
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts
@@ -0,0 +1,35 @@
+/*
+ * 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 {
+ SearchAfterAndBulkCreateParams,
+ SignalSourceHit,
+ WrapHits,
+ WrappedSignalHit,
+} from './types';
+import { generateId } from './utils';
+import { buildBulkBody } from './build_bulk_body';
+import { filterDuplicateSignals } from './filter_duplicate_signals';
+
+export const wrapHitsFactory = ({
+ ruleSO,
+ signalsIndex,
+}: {
+ ruleSO: SearchAfterAndBulkCreateParams['ruleSO'];
+ signalsIndex: string;
+}): WrapHits => (events) => {
+ const wrappedDocs: WrappedSignalHit[] = events.flatMap((doc) => [
+ {
+ _index: signalsIndex,
+ // TODO: bring back doc._version
+ _id: generateId(doc._index, doc._id, '', ruleSO.attributes.params.ruleId ?? ''),
+ _source: buildBulkBody(ruleSO, doc as SignalSourceHit),
+ },
+ ]);
+
+ return filterDuplicateSignals(ruleSO.id, wrappedDocs);
+};
diff --git a/x-pack/plugins/security_solution/server/lib/types.ts b/x-pack/plugins/security_solution/server/lib/types.ts
index f1c7a275e162c..6ef51bc3c53d4 100644
--- a/x-pack/plugins/security_solution/server/lib/types.ts
+++ b/x-pack/plugins/security_solution/server/lib/types.ts
@@ -17,6 +17,7 @@ import { Notes } from './timeline/saved_object/notes';
import { PinnedEvent } from './timeline/saved_object/pinned_events';
import { Timeline } from './timeline/saved_object/timelines';
import { TotalValue, BaseHit, Explanation } from '../../common/detection_engine/types';
+import { SignalHit } from './detection_engine/signals/types';
export interface AppDomainLibs {
fields: IndexFields;
@@ -100,6 +101,8 @@ export interface SearchResponse extends BaseSearchResponse {
export type SearchHit = SearchResponse['hits']['hits'][0];
+export type SearchSignalHit = SearchResponse['hits']['hits'][0];
+
export interface TermAggregationBucket {
key: string;
doc_count: number;
diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts
index 609ee88c319f9..a0f466512cc1d 100644
--- a/x-pack/plugins/security_solution/server/plugin.ts
+++ b/x-pack/plugins/security_solution/server/plugin.ts
@@ -260,6 +260,8 @@ export class Plugin implements IPlugin
Date: Mon, 14 Jun 2021 20:25:04 +0200
Subject: [PATCH 15/91] [Discover] Unskip runtime field editor test (#101059)
---
.../apps/discover/_runtime_fields_editor.ts | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/test/functional/apps/discover/_runtime_fields_editor.ts b/test/functional/apps/discover/_runtime_fields_editor.ts
index 648fa3efe337c..46fe5c34f4cf3 100644
--- a/test/functional/apps/discover/_runtime_fields_editor.ts
+++ b/test/functional/apps/discover/_runtime_fields_editor.ts
@@ -31,7 +31,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await fieldEditor.save();
};
- describe.skip('discover integration with runtime fields editor', function describeIndexTests() {
+ describe('discover integration with runtime fields editor', function describeIndexTests() {
before(async function () {
await esArchiver.load('test/functional/fixtures/es_archiver/discover');
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
@@ -104,7 +104,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
});
- // flaky https://github.com/elastic/kibana/issues/100966
it('doc view includes runtime fields', async function () {
// navigate to doc view
const table = await PageObjects.discover.getDocTable();
@@ -121,10 +120,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await rowActions[idxToClick].click();
});
- const hasDocHit = await testSubjects.exists('doc-hit');
- expect(hasDocHit).to.be(true);
- const runtimeFieldsRow = await testSubjects.exists('tableDocViewRow-discover runtimefield');
- expect(runtimeFieldsRow).to.be(true);
+ await retry.waitFor('doc viewer is displayed with runtime field', async () => {
+ const hasDocHit = await testSubjects.exists('doc-hit');
+ if (!hasDocHit) {
+ // Maybe loading has not completed
+ throw new Error('test subject doc-hit is not yet displayed');
+ }
+ const runtimeFieldsRow = await testSubjects.exists('tableDocViewRow-discover runtimefield');
+
+ return hasDocHit && runtimeFieldsRow;
+ });
});
});
}
From 8f5dad98a181eed7c4b1efe6c1cf41f906cfd042 Mon Sep 17 00:00:00 2001
From: Kerry Gallagher <471693+Kerry350@users.noreply.github.com>
Date: Mon, 14 Jun 2021 19:28:08 +0100
Subject: [PATCH 16/91] [Logs / Metrics UI] Convert logs and metrics pages to
the new Observability page template (#101239)
* Convert Logs and Metrics pages to use the Observability page template
---
x-pack/plugins/infra/kibana.json | 5 +-
.../metric_anomaly/components/expression.tsx | 4 +-
.../infra/public/apps/common_styles.ts | 13 +-
x-pack/plugins/infra/public/apps/logs_app.tsx | 2 +-
.../plugins/infra/public/apps/metrics_app.tsx | 6 +-
.../public/assets/anomaly_chart_minified.svg | 1 -
.../components/empty_states/no_indices.tsx | 28 +--
.../infra/public/components/error_page.tsx | 97 +++-----
.../infra/public/components/loading_page.tsx | 44 ++--
.../logging/log_analysis_setup/index.ts | 2 -
.../logging/log_analysis_setup/setup_page.tsx | 60 -----
.../logging/log_source_error_page.tsx | 14 +-
.../components/navigation/app_navigation.tsx | 33 ---
.../components/navigation/routed_tabs.tsx | 62 -----
.../infra/public/components/page_template.tsx | 22 ++
.../subscription_splash_content.tsx | 123 +++-------
.../infra/public/components/toolbar_panel.ts | 27 --
x-pack/plugins/infra/public/index.scss | 16 --
.../pages/link_to/link_to_logs.test.tsx | 16 +-
.../pages/logs/log_entry_categories/page.tsx | 5 +-
.../log_entry_categories/page_content.tsx | 61 ++++-
.../page_results_content.tsx | 77 ++++--
.../page_setup_content.tsx | 34 ++-
.../top_categories/top_categories_section.tsx | 68 +----
.../public/pages/logs/log_entry_rate/page.tsx | 5 +-
.../logs/log_entry_rate/page_content.tsx | 58 ++++-
.../log_entry_rate/page_results_content.tsx | 160 ++++++------
.../log_entry_rate/page_setup_content.tsx | 34 ++-
.../sections/anomalies/index.tsx | 19 --
.../infra/public/pages/logs/page_content.tsx | 25 +-
.../infra/public/pages/logs/page_template.tsx | 24 ++
.../source_configuration_settings.tsx | 196 +++++++--------
.../infra/public/pages/logs/stream/page.tsx | 7 +-
.../public/pages/logs/stream/page_content.tsx | 33 ++-
.../pages/logs/stream/page_logs_content.tsx | 4 +-
.../public/pages/logs/stream/page_toolbar.tsx | 7 +-
.../infra/public/pages/metrics/index.tsx | 199 +++++++--------
.../components/bottom_drawer.tsx | 4 +-
.../inventory_view/components/filter_bar.tsx | 24 +-
.../ml/anomaly_detection/flyout_home.tsx | 8 +-
.../waffle/waffle_time_controls.tsx | 13 +-
.../pages/metrics/inventory_view/index.tsx | 157 +++++++-----
.../components/node_details_page.tsx | 118 ++++-----
.../metric_detail/components/side_nav.tsx | 25 +-
.../pages/metrics/metric_detail/index.tsx | 80 +++---
.../metrics_explorer/components/toolbar.tsx | 5 +-
.../pages/metrics/metrics_explorer/index.tsx | 75 +++---
.../public/pages/metrics/page_template.tsx | 24 ++
.../source_configuration_settings.tsx | 232 +++++++++---------
x-pack/plugins/infra/public/plugin.ts | 59 ++++-
.../public/components/shared/index.tsx | 1 +
.../components/shared/page_template/index.ts | 1 +
.../shared/page_template/page_template.tsx | 1 +
x-pack/plugins/observability/public/index.ts | 2 +
.../translations/translations/ja-JP.json | 6 -
.../translations/translations/zh-CN.json | 6 -
.../page_objects/infra_home_page.ts | 24 +-
57 files changed, 1149 insertions(+), 1307 deletions(-)
delete mode 100644 x-pack/plugins/infra/public/assets/anomaly_chart_minified.svg
delete mode 100644 x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_page.tsx
delete mode 100644 x-pack/plugins/infra/public/components/navigation/app_navigation.tsx
delete mode 100644 x-pack/plugins/infra/public/components/navigation/routed_tabs.tsx
create mode 100644 x-pack/plugins/infra/public/components/page_template.tsx
delete mode 100644 x-pack/plugins/infra/public/components/toolbar_panel.ts
create mode 100644 x-pack/plugins/infra/public/pages/logs/page_template.tsx
create mode 100644 x-pack/plugins/infra/public/pages/metrics/page_template.tsx
diff --git a/x-pack/plugins/infra/kibana.json b/x-pack/plugins/infra/kibana.json
index c0d567ef83ced..ec1b11c90f7a3 100644
--- a/x-pack/plugins/infra/kibana.json
+++ b/x-pack/plugins/infra/kibana.json
@@ -11,9 +11,10 @@
"dataEnhanced",
"visTypeTimeseries",
"alerting",
- "triggersActionsUi"
+ "triggersActionsUi",
+ "observability"
],
- "optionalPlugins": ["ml", "observability", "home", "embeddable"],
+ "optionalPlugins": ["ml", "home", "embeddable"],
"server": true,
"ui": true,
"configPath": ["xpack", "infra"],
diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx
index afbd6ffa8b5f7..e44a747aa07e7 100644
--- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx
+++ b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx
@@ -11,7 +11,7 @@ import { EuiFlexGroup, EuiSpacer, EuiText, EuiLoadingContent } from '@elastic/eu
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { useInfraMLCapabilities } from '../../../containers/ml/infra_ml_capabilities';
-import { SubscriptionSplashContent } from '../../../components/subscription_splash_content';
+import { SubscriptionSplashPrompt } from '../../../components/subscription_splash_content';
import { AlertPreview } from '../../common';
import {
METRIC_ANOMALY_ALERT_TYPE_ID,
@@ -185,7 +185,7 @@ export const Expression: React.FC = (props) => {
}, [metadata, derivedIndexPattern, defaultExpression, source, space]); // eslint-disable-line react-hooks/exhaustive-deps
if (isLoadingMLCapabilities) return ;
- if (!hasInfraMLCapabilities) return ;
+ if (!hasInfraMLCapabilities) return ;
return (
// https://github.com/elastic/kibana/issues/89506
diff --git a/x-pack/plugins/infra/public/apps/common_styles.ts b/x-pack/plugins/infra/public/apps/common_styles.ts
index be12c6cdc937f..68c820d538ca9 100644
--- a/x-pack/plugins/infra/public/apps/common_styles.ts
+++ b/x-pack/plugins/infra/public/apps/common_styles.ts
@@ -5,10 +5,15 @@
* 2.0.
*/
+import { APP_WRAPPER_CLASS } from '../../../../../src/core/public';
+
export const CONTAINER_CLASSNAME = 'infra-container-element';
-export const prepareMountElement = (element: HTMLElement) => {
- // Ensure the element we're handed from application mounting is assigned a class
- // for our index.scss styles to apply to.
- element.classList.add(CONTAINER_CLASSNAME);
+export const prepareMountElement = (element: HTMLElement, testSubject?: string) => {
+ // Ensure all wrapping elements have the APP_WRAPPER_CLASS so that the KinanaPageTemplate works as expected
+ element.classList.add(APP_WRAPPER_CLASS);
+
+ if (testSubject) {
+ element.setAttribute('data-test-subj', testSubject);
+ }
};
diff --git a/x-pack/plugins/infra/public/apps/logs_app.tsx b/x-pack/plugins/infra/public/apps/logs_app.tsx
index 61082efe43647..b512b5ce4a176 100644
--- a/x-pack/plugins/infra/public/apps/logs_app.tsx
+++ b/x-pack/plugins/infra/public/apps/logs_app.tsx
@@ -27,7 +27,7 @@ export const renderApp = (
) => {
const storage = new Storage(window.localStorage);
- prepareMountElement(element);
+ prepareMountElement(element, 'infraLogsPage');
ReactDOM.render(
{
const storage = new Storage(window.localStorage);
- prepareMountElement(element);
+ prepareMountElement(element, 'infraMetricsPage');
ReactDOM.render(
)}
- {uiCapabilities?.infrastructure?.show && (
-
- )}
{uiCapabilities?.infrastructure?.show && (
)}
diff --git a/x-pack/plugins/infra/public/assets/anomaly_chart_minified.svg b/x-pack/plugins/infra/public/assets/anomaly_chart_minified.svg
deleted file mode 100644
index dd1b39248bba2..0000000000000
--- a/x-pack/plugins/infra/public/assets/anomaly_chart_minified.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/x-pack/plugins/infra/public/components/empty_states/no_indices.tsx b/x-pack/plugins/infra/public/components/empty_states/no_indices.tsx
index 264428e44a44a..c61a567ac73b1 100644
--- a/x-pack/plugins/infra/public/components/empty_states/no_indices.tsx
+++ b/x-pack/plugins/infra/public/components/empty_states/no_indices.tsx
@@ -7,8 +7,7 @@
import { EuiEmptyPrompt } from '@elastic/eui';
import React from 'react';
-
-import { euiStyled } from '../../../../../../src/plugins/kibana_react/common';
+import { PageTemplate } from '../page_template';
interface NoIndicesProps {
message: string;
@@ -17,15 +16,16 @@ interface NoIndicesProps {
'data-test-subj'?: string;
}
-export const NoIndices: React.FC = ({ actions, message, title, ...rest }) => (
- {title}}
- body={{message}
}
- actions={actions}
- {...rest}
- />
-);
-
-const CenteredEmptyPrompt = euiStyled(EuiEmptyPrompt)`
- align-self: center;
-`;
+// Represents a fully constructed page, including page template.
+export const NoIndices: React.FC = ({ actions, message, title, ...rest }) => {
+ return (
+
+ {title}}
+ body={{message}
}
+ actions={actions}
+ {...rest}
+ />
+
+ );
+};
diff --git a/x-pack/plugins/infra/public/components/error_page.tsx b/x-pack/plugins/infra/public/components/error_page.tsx
index 184901b4fdd9b..da6716ddc7f72 100644
--- a/x-pack/plugins/infra/public/components/error_page.tsx
+++ b/x-pack/plugins/infra/public/components/error_page.tsx
@@ -5,20 +5,10 @@
* 2.0.
*/
-import {
- EuiButton,
- EuiCallOut,
- EuiFlexGroup,
- EuiFlexItem,
- EuiPageBody,
- EuiPageContent,
- EuiPageContentBody,
- EuiSpacer,
-} from '@elastic/eui';
+import { EuiButton, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
-import { euiStyled } from '../../../../../src/plugins/kibana_react/common';
-import { FlexPage } from './page';
+import { PageTemplate } from './page_template';
interface Props {
detailedMessage?: React.ReactNode;
@@ -26,51 +16,40 @@ interface Props {
shortMessage: React.ReactNode;
}
-export const ErrorPage: React.FC = ({ detailedMessage, retry, shortMessage }) => (
-
-
- = ({ detailedMessage, retry, shortMessage }) => {
+ return (
+
+
+ }
>
-
-
- }
- >
-
- {shortMessage}
- {retry ? (
-
-
-
-
-
- ) : null}
-
- {detailedMessage ? (
- <>
-
- {detailedMessage}
- >
- ) : null}
-
-
-
-
-
-);
-
-const MinimumPageContent = euiStyled(EuiPageContent)`
- min-width: 50vh;
-`;
+
+ {shortMessage}
+ {retry ? (
+
+
+
+
+
+ ) : null}
+
+ {detailedMessage ? (
+ <>
+
+ {detailedMessage}
+ >
+ ) : null}
+
+
+ );
+};
diff --git a/x-pack/plugins/infra/public/components/loading_page.tsx b/x-pack/plugins/infra/public/components/loading_page.tsx
index 755511374b75f..2b2859707a20d 100644
--- a/x-pack/plugins/infra/public/components/loading_page.tsx
+++ b/x-pack/plugins/infra/public/components/loading_page.tsx
@@ -5,34 +5,38 @@
* 2.0.
*/
-import {
- EuiFlexGroup,
- EuiFlexItem,
- EuiLoadingSpinner,
- EuiPageBody,
- EuiPageContent,
-} from '@elastic/eui';
+import { EuiEmptyPrompt, EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { ReactNode } from 'react';
-
-import { FlexPage } from './page';
+import { PageTemplate } from './page_template';
interface LoadingPageProps {
message?: ReactNode;
'data-test-subj'?: string;
}
+// Represents a fully constructed page, including page template.
export const LoadingPage = ({
message,
'data-test-subj': dataTestSubj = 'loadingPage',
-}: LoadingPageProps) => (
-
-
-
-
-
- {message}
+}: LoadingPageProps) => {
+ return (
+
+
+
+ );
+};
+
+export const LoadingPrompt = ({ message }: LoadingPageProps) => {
+ return (
+
+
+
+
+ {message}
-
-
-
-);
+ }
+ />
+ );
+};
diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/index.ts b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/index.ts
index db5a996c604fc..9ca08c69cf600 100644
--- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/index.ts
+++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/index.ts
@@ -5,8 +5,6 @@
* 2.0.
*/
-export * from './setup_page';
-
export * from './initial_configuration_step';
export * from './process_step';
diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_page.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_page.tsx
deleted file mode 100644
index a998d0c304a5e..0000000000000
--- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_page.tsx
+++ /dev/null
@@ -1,60 +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 {
- CommonProps,
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiPageContentBody,
- EuiPageContentHeader,
- EuiPageContentHeaderSection,
- EuiTitle,
-} from '@elastic/eui';
-import React from 'react';
-
-import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
-
-export const LogAnalysisSetupPage: React.FunctionComponent = ({
- children,
- ...rest
-}) => {
- return (
-
-
-
- {children}
-
-
-
- );
-};
-
-export const LogAnalysisSetupPageHeader: React.FunctionComponent = ({ children }) => (
-
-
-
- {children}
-
-
-
-);
-
-export const LogAnalysisSetupPageContent = EuiPageContentBody;
-
-// !important due to https://github.com/elastic/eui/issues/2232
-const LogEntryRateSetupPageContent = euiStyled(EuiPageContent)`
- max-width: 768px !important;
-`;
-
-const LogEntryRateSetupPage = euiStyled(EuiPage)`
- height: 100%;
-`;
diff --git a/x-pack/plugins/infra/public/components/logging/log_source_error_page.tsx b/x-pack/plugins/infra/public/components/logging/log_source_error_page.tsx
index 8ea35fd8f259f..6c757f7383a06 100644
--- a/x-pack/plugins/infra/public/components/logging/log_source_error_page.tsx
+++ b/x-pack/plugins/infra/public/components/logging/log_source_error_page.tsx
@@ -5,14 +5,7 @@
* 2.0.
*/
-import {
- EuiButton,
- EuiButtonEmpty,
- EuiCallOut,
- EuiEmptyPrompt,
- EuiPageTemplate,
- EuiSpacer,
-} from '@elastic/eui';
+import { EuiButton, EuiButtonEmpty, EuiCallOut, EuiEmptyPrompt, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { SavedObjectNotFound } from '../../../../../../src/plugins/kibana_utils/common';
@@ -22,6 +15,7 @@ import {
ResolveLogSourceConfigurationError,
} from '../../../common/log_sources';
import { useLinkProps } from '../../hooks/use_link_props';
+import { LogsPageTemplate } from '../../pages/logs/page_template';
export const LogSourceErrorPage: React.FC<{
errors: Error[];
@@ -30,7 +24,7 @@ export const LogSourceErrorPage: React.FC<{
const settingsLinkProps = useLinkProps({ app: 'logs', pathname: '/settings' });
return (
-
+
,
]}
/>
-
+
);
};
diff --git a/x-pack/plugins/infra/public/components/navigation/app_navigation.tsx b/x-pack/plugins/infra/public/components/navigation/app_navigation.tsx
deleted file mode 100644
index 966b91537d3bd..0000000000000
--- a/x-pack/plugins/infra/public/components/navigation/app_navigation.tsx
+++ /dev/null
@@ -1,33 +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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import React from 'react';
-import { euiStyled } from '../../../../../../src/plugins/kibana_react/common';
-
-interface AppNavigationProps {
- 'aria-label': string;
- children: React.ReactNode;
-}
-
-export const AppNavigation = ({ 'aria-label': label, children }: AppNavigationProps) => (
-
-
- {children}
-
-
-);
-
-const Nav = euiStyled.nav`
- background: ${(props) => props.theme.eui.euiColorEmptyShade};
- border-bottom: ${(props) => props.theme.eui.euiBorderThin};
- padding: ${(props) => `${props.theme.eui.euiSizeS} ${props.theme.eui.euiSizeL}`};
- .euiTabs {
- padding-left: 3px;
- margin-left: -3px;
- };
-`;
diff --git a/x-pack/plugins/infra/public/components/navigation/routed_tabs.tsx b/x-pack/plugins/infra/public/components/navigation/routed_tabs.tsx
deleted file mode 100644
index 2a5ffcd826e7c..0000000000000
--- a/x-pack/plugins/infra/public/components/navigation/routed_tabs.tsx
+++ /dev/null
@@ -1,62 +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 { EuiLink, EuiTab, EuiTabs } from '@elastic/eui';
-import React from 'react';
-import { Route } from 'react-router-dom';
-
-import { euiStyled } from '../../../../../../src/plugins/kibana_react/common';
-import { useLinkProps } from '../../hooks/use_link_props';
-import { LinkDescriptor } from '../../hooks/use_link_props';
-
-interface TabConfig {
- title: string | React.ReactNode;
-}
-
-type TabConfiguration = TabConfig & LinkDescriptor;
-
-interface RoutedTabsProps {
- tabs: TabConfiguration[];
-}
-
-const noop = () => {};
-
-export const RoutedTabs = ({ tabs }: RoutedTabsProps) => {
- return (
-
- {tabs.map((tab) => {
- return ;
- })}
-
- );
-};
-
-const Tab = ({ title, pathname, app }: TabConfiguration) => {
- const linkProps = useLinkProps({ app, pathname });
- return (
- {
- return (
-
-
-
- {title}
-
-
-
- );
- }}
- />
- );
-};
-
-const TabContainer = euiStyled.div`
- .euiLink {
- color: inherit !important;
- }
-`;
diff --git a/x-pack/plugins/infra/public/components/page_template.tsx b/x-pack/plugins/infra/public/components/page_template.tsx
new file mode 100644
index 0000000000000..1a10a6cd831b9
--- /dev/null
+++ b/x-pack/plugins/infra/public/components/page_template.tsx
@@ -0,0 +1,22 @@
+/*
+ * 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 { useKibanaContextForPlugin } from '../hooks/use_kibana';
+import type { LazyObservabilityPageTemplateProps } from '../../../observability/public';
+
+export const PageTemplate: React.FC = (pageTemplateProps) => {
+ const {
+ services: {
+ observability: {
+ navigation: { PageTemplate: Template },
+ },
+ },
+ } = useKibanaContextForPlugin();
+
+ return ;
+};
diff --git a/x-pack/plugins/infra/public/components/subscription_splash_content.tsx b/x-pack/plugins/infra/public/components/subscription_splash_content.tsx
index a6477dfc7d172..5d7f28e7d2f84 100644
--- a/x-pack/plugins/infra/public/components/subscription_splash_content.tsx
+++ b/x-pack/plugins/infra/public/components/subscription_splash_content.tsx
@@ -7,28 +7,30 @@
import React, { useEffect } from 'react';
import { i18n } from '@kbn/i18n';
-import {
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiFlexGroup,
- EuiFlexItem,
- EuiSpacer,
- EuiTitle,
- EuiText,
- EuiButton,
- EuiButtonEmpty,
- EuiImage,
-} from '@elastic/eui';
+import { EuiText, EuiButton, EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
-import { euiStyled, EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common';
import { HttpStart } from '../../../../../src/core/public';
import { useTrialStatus } from '../hooks/use_trial_status';
-import { LoadingPage } from '../components/loading_page';
+import { LoadingPrompt } from '../components/loading_page';
+import { PageTemplate } from './page_template';
+import type { LazyObservabilityPageTemplateProps } from '../../../observability/public';
+
+const loadingMessage = i18n.translate('xpack.infra.ml.splash.loadingMessage', {
+ defaultMessage: 'Checking license...',
+});
+
+export const SubscriptionSplashPage: React.FC = (
+ templateProps
+) => {
+ return (
+
+
+
+ );
+};
-export const SubscriptionSplashContent: React.FC = () => {
+export const SubscriptionSplashPrompt: React.FC = () => {
const { services } = useKibana<{ http: HttpStart }>();
const { loadState, isTrialAvailable, checkTrialAvailability } = useTrialStatus();
@@ -37,13 +39,7 @@ export const SubscriptionSplashContent: React.FC = () => {
}, [checkTrialAvailability]);
if (loadState === 'pending') {
- return (
-
- );
+ return ;
}
const canStartTrial = isTrialAvailable && loadState === 'resolved';
@@ -102,74 +98,15 @@ export const SubscriptionSplashContent: React.FC = () => {
}
return (
-
-
-
-
-
-
-
- {title}
-
-
-
- {description}
-
-
- {cta}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {title}}
+ body={
+
+ {description}
+
+ }
+ actions={cta}
+ />
);
};
-
-const SubscriptionPage = euiStyled(EuiPage)`
- height: 100%
-`;
-
-const SubscriptionPageContent = euiStyled(EuiPageContent)`
- max-width: 768px !important;
-`;
-
-const SubscriptionPageFooter = euiStyled.div`
- background: ${(props) => props.theme.eui.euiColorLightestShade};
- margin: 0 -${(props) => props.theme.eui.paddingSizes.l} -${(props) =>
- props.theme.eui.paddingSizes.l};
- padding: ${(props) => props.theme.eui.paddingSizes.l};
-`;
diff --git a/x-pack/plugins/infra/public/components/toolbar_panel.ts b/x-pack/plugins/infra/public/components/toolbar_panel.ts
deleted file mode 100644
index d94e7faa0eabf..0000000000000
--- a/x-pack/plugins/infra/public/components/toolbar_panel.ts
+++ /dev/null
@@ -1,27 +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 { FunctionComponent } from 'react';
-import { EuiPanel } from '@elastic/eui';
-import { StyledComponent } from 'styled-components';
-import { EuiTheme, euiStyled } from '../../../../../src/plugins/kibana_react/common';
-
-// The return type of this component needs to be specified because the inferred
-// return type depends on types that are not exported from EUI. You get a TS4023
-// error if the return type is not specified.
-export const ToolbarPanel: StyledComponent = euiStyled(EuiPanel).attrs(
- () => ({
- grow: false,
- paddingSize: 'none',
- })
-)`
- border-top: none;
- border-right: none;
- border-left: none;
- border-radius: 0;
- padding: ${(props) => `12px ${props.theme.eui.paddingSizes.m}`};
-`;
diff --git a/x-pack/plugins/infra/public/index.scss b/x-pack/plugins/infra/public/index.scss
index 990092c792b14..e69de29bb2d1d 100644
--- a/x-pack/plugins/infra/public/index.scss
+++ b/x-pack/plugins/infra/public/index.scss
@@ -1,16 +0,0 @@
-/* Infra plugin styles */
-
-.infra-container-element {
- background-color: $euiColorEmptyShade;
- position: absolute;
- top: 0;
- left: 0;
- bottom: 0;
- right: 0;
- align-items: flex-start;
- flex: 1 0 auto;
- overflow-x: hidden;
- overflow-y: auto;
- display: flex;
- flex-direction: column;
-}
diff --git a/x-pack/plugins/infra/public/pages/link_to/link_to_logs.test.tsx b/x-pack/plugins/infra/public/pages/link_to/link_to_logs.test.tsx
index 91f42509d493a..f9c80edd2c199 100644
--- a/x-pack/plugins/infra/public/pages/link_to/link_to_logs.test.tsx
+++ b/x-pack/plugins/infra/public/pages/link_to/link_to_logs.test.tsx
@@ -5,12 +5,12 @@
* 2.0.
*/
-import { render } from '@testing-library/react';
+import { render, waitFor } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import React from 'react';
import { Route, Router, Switch } from 'react-router-dom';
import { httpServiceMock } from 'src/core/public/mocks';
-import { KibanaContextProvider } from 'src/plugins/kibana_react/public';
+import { KibanaContextProvider, KibanaPageTemplate } from 'src/plugins/kibana_react/public';
import { useLogSource } from '../../containers/logs/log_source';
import {
createLoadedUseLogSourceMock,
@@ -28,6 +28,11 @@ const renderRoutes = (routes: React.ReactElement) => {
data: {
indexPatterns: {},
},
+ observability: {
+ navigation: {
+ PageTemplate: KibanaPageTemplate,
+ },
+ },
};
const renderResult = render(
@@ -193,7 +198,7 @@ describe('LinkToLogsPage component', () => {
expect(searchParams.get('logPosition')).toEqual(null);
});
- it('renders a loading page while loading the source configuration', () => {
+ it('renders a loading page while loading the source configuration', async () => {
useLogSourceMock.mockImplementation(createLoadingUseLogSourceMock());
const { history, queryByTestId } = renderRoutes(
@@ -203,8 +208,9 @@ describe('LinkToLogsPage component', () => {
);
history.push('/link-to/host-logs/HOST_NAME');
-
- expect(queryByTestId('nodeLoadingPage-host')).not.toBeEmpty();
+ await waitFor(() => {
+ expect(queryByTestId('nodeLoadingPage-host')).not.toBeEmpty();
+ });
});
});
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page.tsx
index 6c816db4f5660..64dbcbdfe2258 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page.tsx
@@ -7,7 +7,6 @@
import { EuiErrorBoundary } from '@elastic/eui';
import React from 'react';
-import { ColumnarPage } from '../../../components/page';
import { LogEntryCategoriesPageContent } from './page_content';
import { LogEntryCategoriesPageProviders } from './page_providers';
@@ -15,9 +14,7 @@ export const LogEntryCategoriesPage = () => {
return (
-
-
-
+
);
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx
index 1762caed14a67..462b8b2f9dc3e 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx
@@ -18,11 +18,17 @@ import {
LogAnalysisSetupFlyout,
useLogAnalysisSetupFlyoutStateContext,
} from '../../../components/logging/log_analysis_setup/setup_flyout';
-import { SubscriptionSplashContent } from '../../../components/subscription_splash_content';
+import { SubscriptionSplashPage } from '../../../components/subscription_splash_content';
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis';
import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
import { LogEntryCategoriesResultsContent } from './page_results_content';
import { LogEntryCategoriesSetupContent } from './page_setup_content';
+import { LogsPageTemplate } from '../page_template';
+import type { LazyObservabilityPageTemplateProps } from '../../../../../observability/public';
+
+const logCategoriesTitle = i18n.translate('xpack.infra.logs.logCategoriesTitle', {
+ defaultMessage: 'Categories',
+});
export const LogEntryCategoriesPageContent = () => {
const {
@@ -45,9 +51,20 @@ export const LogEntryCategoriesPageContent = () => {
}, [fetchJobStatus, hasLogAnalysisReadCapabilities]);
if (!hasLogAnalysisCapabilites) {
- return ;
+ return (
+
+ );
} else if (!hasLogAnalysisReadCapabilities) {
- return ;
+ return (
+
+
+
+ );
} else if (setupStatus.type === 'initializing') {
return (
{
/>
);
} else if (setupStatus.type === 'unknown') {
- return ;
+ return (
+
+
+
+ );
} else if (isJobStatusWithResults(jobStatus['log-entry-categories-count'])) {
return (
<>
-
+
>
);
} else if (!hasLogAnalysisSetupCapabilities) {
- return ;
+ return (
+
+
+
+ );
} else {
return (
<>
-
+
+
+
>
);
@@ -78,3 +108,20 @@ export const LogEntryCategoriesPageContent = () => {
};
const allowedSetupModules = ['logs_ui_categories' as const];
+
+const CategoriesPageTemplate: React.FC = ({
+ children,
+ ...rest
+}) => {
+ return (
+
+ {children}
+
+ );
+};
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx
index 1206e5c365441..7098f457117d3 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx
@@ -6,7 +6,7 @@
*/
import datemath from '@elastic/datemath';
-import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiPanel, EuiSuperDatePicker } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiSuperDatePicker } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import moment from 'moment';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
@@ -26,19 +26,31 @@ import {
useLogEntryCategoriesResultsUrlState,
} from './use_log_entry_categories_results_url_state';
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis/log_analysis_capabilities';
+import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
+import { LogsPageTemplate } from '../page_template';
+import { RecreateJobButton } from '../../../components/logging/log_analysis_setup/create_job_button';
+import { AnalyzeInMlButton } from '../../../components/logging/log_analysis_results';
+import { useMlHref, ML_PAGES } from '../../../../../ml/public';
+import { DatasetsSelector } from '../../../components/logging/log_analysis_results/datasets_selector';
const JOB_STATUS_POLLING_INTERVAL = 30000;
interface LogEntryCategoriesResultsContentProps {
onOpenSetup: () => void;
+ pageTitle: string;
}
export const LogEntryCategoriesResultsContent: React.FunctionComponent = ({
onOpenSetup,
+ pageTitle,
}) => {
useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results' });
useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results', delay: 15000 });
+ const {
+ services: { ml, http },
+ } = useKibanaContextForPlugin();
+
const { hasLogAnalysisSetupCapabilities } = useLogAnalysisCapabilitiesContext();
const {
@@ -178,17 +190,48 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent
-
+ ,
+ ,
+ ],
+ }}
+ >
-
+
+
+
-
-
-
+
-
+
);
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_setup_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_setup_content.tsx
index cf6f31811c123..18adfaad03ef8 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_setup_content.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_setup_content.tsx
@@ -7,13 +7,7 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiText, EuiButton, EuiSpacer } from '@elastic/eui';
-
-import {
- LogAnalysisSetupPage,
- LogAnalysisSetupPageContent,
- LogAnalysisSetupPageHeader,
-} from '../../../components/logging/log_analysis_setup';
+import { EuiText, EuiButton, EuiEmptyPrompt } from '@elastic/eui';
import { useTrackPageview } from '../../../../../observability/public';
interface LogEntryCategoriesSetupContentProps {
@@ -27,14 +21,17 @@ export const LogEntryCategoriesSetupContent: React.FunctionComponent
-
-
-
-
+
+
+
+ }
+ body={
-
+ }
+ actions={
-
-
+ }
+ />
);
};
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx
index f5b94bce74e67..0aabc570a89e8 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx
@@ -5,97 +5,35 @@
* 2.0.
*/
-import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSpacer, EuiTitle } from '@elastic/eui';
-import moment from 'moment';
+import { EuiLoadingSpinner } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { LogEntryCategory } from '../../../../../../common/log_analysis';
import { TimeRange } from '../../../../../../common/time';
-import { BetaBadge } from '../../../../../components/beta_badge';
import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper';
-import { RecreateJobButton } from '../../../../../components/logging/log_analysis_setup/create_job_button';
-import { AnalyzeInMlButton } from '../../../../../components/logging/log_analysis_results';
-import { DatasetsSelector } from '../../../../../components/logging/log_analysis_results/datasets_selector';
import { TopCategoriesTable } from './top_categories_table';
import { SortOptions, ChangeSortOptions } from '../../use_log_entry_categories_results';
-import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana';
-import { useMlHref, ML_PAGES } from '../../../../../../../ml/public';
export const TopCategoriesSection: React.FunctionComponent<{
- availableDatasets: string[];
- hasSetupCapabilities: boolean;
- isLoadingDatasets?: boolean;
isLoadingTopCategories?: boolean;
jobId: string;
- onChangeDatasetSelection: (datasets: string[]) => void;
- onRequestRecreateMlJob: () => void;
- selectedDatasets: string[];
sourceId: string;
timeRange: TimeRange;
topCategories: LogEntryCategory[];
sortOptions: SortOptions;
changeSortOptions: ChangeSortOptions;
}> = ({
- availableDatasets,
- hasSetupCapabilities,
- isLoadingDatasets = false,
isLoadingTopCategories = false,
jobId,
- onChangeDatasetSelection,
- onRequestRecreateMlJob,
- selectedDatasets,
sourceId,
timeRange,
topCategories,
sortOptions,
changeSortOptions,
}) => {
- const {
- services: { ml, http },
- } = useKibanaContextForPlugin();
-
- const analyzeInMlLink = useMlHref(ml, http.basePath.get(), {
- page: ML_PAGES.ANOMALY_EXPLORER,
- pageState: {
- jobIds: [jobId],
- timeRange: {
- from: moment(timeRange.startTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
- to: moment(timeRange.endTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
- mode: 'absolute',
- },
- },
- });
-
return (
<>
-
-
-
-
- {title}
-
-
-
-
-
-
-
-
-
-
-
-
-
}
@@ -113,10 +51,6 @@ export const TopCategoriesSection: React.FunctionComponent<{
);
};
-const title = i18n.translate('xpack.infra.logs.logEntryCategories.topCategoriesSectionTitle', {
- defaultMessage: 'Log message categories',
-});
-
const loadingAriaLabel = i18n.translate(
'xpack.infra.logs.logEntryCategories.topCategoriesSectionLoadingAriaLabel',
{ defaultMessage: 'Loading message categories' }
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page.tsx
index 24116a8dbbf3b..ff4cba731b616 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page.tsx
@@ -7,7 +7,6 @@
import { EuiErrorBoundary } from '@elastic/eui';
import React from 'react';
-import { ColumnarPage } from '../../../components/page';
import { LogEntryRatePageContent } from './page_content';
import { LogEntryRatePageProviders } from './page_providers';
@@ -15,9 +14,7 @@ export const LogEntryRatePage = () => {
return (
-
-
-
+
);
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx
index 061a2ba0acc1d..ea60d073c2311 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx
@@ -19,15 +19,21 @@ import {
LogAnalysisSetupFlyout,
useLogAnalysisSetupFlyoutStateContext,
} from '../../../components/logging/log_analysis_setup/setup_flyout';
-import { SubscriptionSplashContent } from '../../../components/subscription_splash_content';
+import { SubscriptionSplashPage } from '../../../components/subscription_splash_content';
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis';
import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
import { useLogEntryRateModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_rate';
import { LogEntryRateResultsContent } from './page_results_content';
import { LogEntryRateSetupContent } from './page_setup_content';
+import { LogsPageTemplate } from '../page_template';
+import type { LazyObservabilityPageTemplateProps } from '../../../../../observability/public';
const JOB_STATUS_POLLING_INTERVAL = 30000;
+const anomaliesTitle = i18n.translate('xpack.infra.logs.anomaliesPageTitle', {
+ defaultMessage: 'Anomalies',
+});
+
export const LogEntryRatePageContent = memo(() => {
const {
hasLogAnalysisCapabilites,
@@ -83,9 +89,20 @@ export const LogEntryRatePageContent = memo(() => {
}, JOB_STATUS_POLLING_INTERVAL);
if (!hasLogAnalysisCapabilites) {
- return ;
+ return (
+
+ );
} else if (!hasLogAnalysisReadCapabilities) {
- return ;
+ return (
+
+
+
+ );
} else if (
logEntryCategoriesSetupStatus.type === 'initializing' ||
logEntryRateSetupStatus.type === 'initializing'
@@ -101,25 +118,52 @@ export const LogEntryRatePageContent = memo(() => {
logEntryCategoriesSetupStatus.type === 'unknown' ||
logEntryRateSetupStatus.type === 'unknown'
) {
- return ;
+ return (
+
+
+
+ );
} else if (
isJobStatusWithResults(logEntryCategoriesJobStatus['log-entry-categories-count']) ||
isJobStatusWithResults(logEntryRateJobStatus['log-entry-rate'])
) {
return (
<>
-
+
>
);
} else if (!hasLogAnalysisSetupCapabilities) {
- return ;
+ return (
+
+ ;
+
+ );
} else {
return (
<>
-
+
+
+
>
);
}
});
+
+const AnomaliesPageTemplate: React.FC = ({
+ children,
+ ...rest
+}) => {
+ return (
+
+ {children}
+
+ );
+};
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx
index 0741423fd3886..2d833c87c1e25 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx
@@ -5,16 +5,14 @@
* 2.0.
*/
-import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiPanel, EuiSuperDatePicker } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker } from '@elastic/eui';
import moment from 'moment';
import { stringify } from 'query-string';
import React, { useCallback, useMemo } from 'react';
import { encode, RisonValue } from 'rison-node';
import type { Query } from '../../../../../../../src/plugins/data/public';
-import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { useTrackPageview } from '../../../../../observability/public';
-import { isJobStatusWithResults } from '../../../../common/log_analysis';
import { TimeKey } from '../../../../common/time';
import {
CategoryJobNoticesSection,
@@ -32,6 +30,9 @@ import { AnomaliesResults } from './sections/anomalies';
import { useDatasetFiltering } from './use_dataset_filtering';
import { useLogEntryAnomaliesResults } from './use_log_entry_anomalies_results';
import { useLogAnalysisResultsUrlState } from './use_log_entry_rate_results_url_state';
+import { isJobStatusWithResults } from '../../../../common/log_analysis';
+import { LogsPageTemplate } from '../page_template';
+import { ManageJobsButton } from '../../../components/logging/log_analysis_setup/manage_jobs_button';
export const SORT_DEFAULTS = {
direction: 'desc' as const,
@@ -42,9 +43,12 @@ export const PAGINATION_DEFAULTS = {
pageSize: 25,
};
-export const LogEntryRateResultsContent: React.FunctionComponent = () => {
+export const LogEntryRateResultsContent: React.FunctionComponent<{
+ pageTitle: string;
+}> = ({ pageTitle }) => {
useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_results' });
useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_results', delay: 15000 });
+
const navigateToApp = useKibana().services.application?.navigateToApp;
const { sourceId } = useLogSourceContext();
@@ -188,76 +192,76 @@ export const LogEntryRateResultsContent: React.FunctionComponent = () => {
);
return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ],
+ }}
+ >
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{isLogEntryFlyoutOpen ? (
{
sourceId={sourceId}
/>
) : null}
- >
+
);
};
-
-// This is needed due to the flex-basis: 100% !important; rule that
-// kicks in on small screens via media queries breaking when using direction="column"
-export const ResultsContentPage = euiStyled(EuiPage)`
- flex: 1 0 0%;
-
- .euiFlexGroup--responsive > .euiFlexItem {
- flex-basis: auto !important;
- }
-`;
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_setup_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_setup_content.tsx
index 1754d18596165..9c7b393e4c200 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_setup_content.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_setup_content.tsx
@@ -7,13 +7,7 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiText, EuiButton, EuiSpacer } from '@elastic/eui';
-
-import {
- LogAnalysisSetupPage,
- LogAnalysisSetupPageContent,
- LogAnalysisSetupPageHeader,
-} from '../../../components/logging/log_analysis_setup';
+import { EuiText, EuiButton, EuiEmptyPrompt } from '@elastic/eui';
import { useTrackPageview } from '../../../../../observability/public';
interface LogEntryRateSetupContentProps {
@@ -27,14 +21,17 @@ export const LogEntryRateSetupContent: React.FunctionComponent
-
-
-
-
+
+
+
+ }
+ body={
-
+ }
+ actions={
-
-
+ }
+ />
);
};
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx
index 3bc206e9ad7bb..7741414f79046 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx
@@ -10,7 +10,6 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
- EuiTitle,
EuiLoadingSpinner,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -18,7 +17,6 @@ import React from 'react';
import { TimeRange } from '../../../../../../common/time/time_range';
import { AnomaliesSwimlaneVisualisation } from './anomalies_swimlane_visualisation';
import { AnomaliesTable } from './table';
-import { ManageJobsButton } from '../../../../../components/logging/log_analysis_setup/manage_jobs_button';
import {
ChangePaginationOptions,
ChangeSortOptions,
@@ -36,7 +34,6 @@ export const AnomaliesResults: React.FunctionComponent<{
isLoadingAnomaliesResults: boolean;
anomalies: LogEntryAnomalies;
timeRange: TimeRange;
- onViewModuleList: () => void;
page: Page;
fetchNextPage?: FetchNextPage;
fetchPreviousPage?: FetchPreviousPage;
@@ -50,7 +47,6 @@ export const AnomaliesResults: React.FunctionComponent<{
}> = ({
isLoadingAnomaliesResults,
timeRange,
- onViewModuleList,
anomalies,
changeSortOptions,
sortOptions,
@@ -65,17 +61,6 @@ export const AnomaliesResults: React.FunctionComponent<{
}) => {
return (
<>
-
-
-
- {title}
-
-
-
-
-
-
-
{
const uiCapabilities = useKibana().services.application?.capabilities;
@@ -65,8 +63,13 @@ export const LogsPageContent: React.FunctionComponent = () => {
pathname: '/settings',
};
+ const settingsLinkProps = useLinkProps({
+ app: 'logs',
+ pathname: 'settings',
+ });
+
return (
-
+ <>
@@ -74,6 +77,11 @@ export const LogsPageContent: React.FunctionComponent = () => {
{setHeaderActionMenu && (
+
+
+ {settingsTabTitle}
+
+
@@ -101,13 +109,6 @@ export const LogsPageContent: React.FunctionComponent = () => {
]}
readOnlyBadge={!uiCapabilities?.logs?.save}
/>
-
-
-
-
-
-
-
@@ -117,7 +118,7 @@ export const LogsPageContent: React.FunctionComponent = () => {
-
+ >
);
};
diff --git a/x-pack/plugins/infra/public/pages/logs/page_template.tsx b/x-pack/plugins/infra/public/pages/logs/page_template.tsx
new file mode 100644
index 0000000000000..42ca992e9402d
--- /dev/null
+++ b/x-pack/plugins/infra/public/pages/logs/page_template.tsx
@@ -0,0 +1,24 @@
+/*
+ * 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 { useKibanaContextForPlugin } from '../../hooks/use_kibana';
+import type { LazyObservabilityPageTemplateProps } from '../../../../observability/public';
+
+export const LogsPageTemplate: React.FC = (
+ pageTemplateProps
+) => {
+ const {
+ services: {
+ observability: {
+ navigation: { PageTemplate },
+ },
+ },
+ } = useKibanaContextForPlugin();
+
+ return ;
+};
diff --git a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx
index b295a392c8df9..180949572b086 100644
--- a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx
@@ -10,9 +10,6 @@ import {
EuiErrorBoundary,
EuiFlexGroup,
EuiFlexItem,
- EuiPage,
- EuiPageBody,
- EuiPageContentBody,
EuiPanel,
EuiSpacer,
} from '@elastic/eui';
@@ -29,6 +26,11 @@ import { LogColumnsConfigurationPanel } from './log_columns_configuration_panel'
import { NameConfigurationPanel } from './name_configuration_panel';
import { LogSourceConfigurationFormErrors } from './source_configuration_form_errors';
import { useLogSourceConfigurationFormState } from './source_configuration_form_state';
+import { LogsPageTemplate } from '../page_template';
+
+const settingsTitle = i18n.translate('xpack.infra.logs.settingsTitle', {
+ defaultMessage: 'Settings',
+});
export const LogsSettingsPage = () => {
const uiCapabilities = useKibana().services.application?.capabilities;
@@ -84,102 +86,104 @@ export const LogsSettingsPage = () => {
return (
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {sourceConfigurationFormElement.validity.validity === 'invalid' ? (
+ <>
+
-
-
-
-
-
-
-
-
-
-
-
- {sourceConfigurationFormElement.validity.validity === 'invalid' ? (
- <>
-
-
- >
- ) : null}
-
- {isWriteable && (
-
- {isLoading ? (
-
-
-
- Loading
-
-
-
- ) : (
- <>
-
-
- {
- sourceConfigurationFormElement.resetValue();
- }}
- >
-
-
-
-
-
-
-
-
-
- >
- )}
-
+ >
+ ) : null}
+
+ {isWriteable && (
+
+ {isLoading ? (
+
+
+
+ Loading
+
+
+
+ ) : (
+ <>
+
+
+ {
+ sourceConfigurationFormElement.resetValue();
+ }}
+ >
+
+
+
+
+
+
+
+
+
+ >
)}
-
-
-
-
+
+ )}
+
+
);
};
diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page.tsx
index 10aff0a08d25b..99b66d2d4ab7b 100644
--- a/x-pack/plugins/infra/public/pages/logs/stream/page.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/stream/page.tsx
@@ -8,7 +8,6 @@
import { EuiErrorBoundary } from '@elastic/eui';
import React from 'react';
import { useTrackPageview } from '../../../../../observability/public';
-import { ColumnarPage } from '../../../components/page';
import { StreamPageContent } from './page_content';
import { StreamPageHeader } from './page_header';
import { LogsPageProviders } from './page_providers';
@@ -19,10 +18,8 @@ export const StreamPage = () => {
return (
-
-
-
-
+
+
);
diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_content.tsx
index 5ff07e713233a..527dc79726044 100644
--- a/x-pack/plugins/infra/public/pages/logs/stream/page_content.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/stream/page_content.tsx
@@ -6,11 +6,19 @@
*/
import React from 'react';
+import { i18n } from '@kbn/i18n';
import { LogSourceErrorPage } from '../../../components/logging/log_source_error_page';
import { SourceLoadingPage } from '../../../components/source_loading_page';
import { useLogSourceContext } from '../../../containers/logs/log_source';
import { LogsPageLogsContent } from './page_logs_content';
import { LogsPageNoIndicesContent } from './page_no_indices_content';
+import { LogsPageTemplate } from '../page_template';
+import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
+import { APP_WRAPPER_CLASS } from '../../../../../../../src/core/public';
+
+const streamTitle = i18n.translate('xpack.infra.logs.streamPageTitle', {
+ defaultMessage: 'Stream',
+});
export const StreamPageContent: React.FunctionComponent = () => {
const {
@@ -27,8 +35,31 @@ export const StreamPageContent: React.FunctionComponent = () => {
} else if (hasFailedLoading) {
return ;
} else if (sourceStatus?.logIndexStatus !== 'missing') {
- return ;
+ return (
+
+
+
+
+
+ );
} else {
return ;
}
};
+
+// This is added to facilitate a full height layout whereby the
+// inner container will set it's own height and be scrollable.
+// The "fullHeight" prop won't help us as it only applies to certain breakpoints.
+export const LogStreamPageWrapper = euiStyled.div`
+ .euiPage .euiPageContentBody {
+ display: flex;
+ flex-direction: column;
+ flex: 1 0 auto;
+ width: 100%;
+ height: 100%;
+ }
+`;
diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_logs_content.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_logs_content.tsx
index 165a5a03bcc6e..2af290b816689 100644
--- a/x-pack/plugins/infra/public/pages/logs/stream/page_logs_content.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/stream/page_logs_content.tsx
@@ -5,7 +5,8 @@
* 2.0.
*/
-import React, { useCallback, useContext, useEffect, useMemo } from 'react';
+import { EuiSpacer } from '@elastic/eui';
+import React, { useContext, useCallback, useMemo, useEffect } from 'react';
import usePrevious from 'react-use/lib/usePrevious';
import type { Query } from '../../../../../../../src/plugins/data/public';
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
@@ -208,6 +209,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => {
+
{isFlyoutOpen ? (
{
} = useContext(LogPositionState.Context);
return (
-
-
+
+
{
/>
-
+
);
};
diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx
index 819c764bfb7ba..cda72e96012fe 100644
--- a/x-pack/plugins/infra/public/pages/metrics/index.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx
@@ -15,8 +15,6 @@ import { IIndexPattern } from 'src/plugins/data/common';
import { MetricsSourceConfigurationProperties } from '../../../common/metrics_sources';
import { DocumentTitle } from '../../components/document_title';
import { HelpCenterContent } from '../../components/help_center_content';
-import { RoutedTabs } from '../../components/navigation/routed_tabs';
-import { ColumnarPage } from '../../components/page';
import { Header } from '../../components/header';
import {
MetricsExplorerOptionsContainer,
@@ -27,8 +25,8 @@ import { WithSource } from '../../containers/with_source';
import { Source } from '../../containers/metrics_source';
import { MetricsExplorerPage } from './metrics_explorer';
import { SnapshotPage } from './inventory_view';
+import { MetricDetail } from './metric_detail';
import { MetricsSettingsPage } from './settings';
-import { AppNavigation } from '../../components/navigation/app_navigation';
import { SourceLoadingPage } from '../../components/source_loading_page';
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
import { WaffleOptionsProvider } from './inventory_view/hooks/use_waffle_options';
@@ -42,6 +40,7 @@ import { InfraMLCapabilitiesProvider } from '../../containers/ml/infra_ml_capabi
import { AnomalyDetectionFlyout } from './inventory_view/components/ml/anomaly_detection/anomaly_detection_flyout';
import { HeaderMenuPortal } from '../../../../observability/public';
import { HeaderActionMenuContext } from '../../utils/header_action_menu_provider';
+import { useLinkProps } from '../../hooks/use_link_props';
const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLabel', {
defaultMessage: 'Add data',
@@ -51,8 +50,17 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
const uiCapabilities = useKibana().services.application?.capabilities;
const { setHeaderActionMenu } = useContext(HeaderActionMenuContext);
+ const settingsTabTitle = i18n.translate('xpack.infra.metrics.settingsTabTitle', {
+ defaultMessage: 'Settings',
+ });
+
const kibana = useKibana();
+ const settingsLinkProps = useLinkProps({
+ app: 'metrics',
+ pathname: 'settings',
+ });
+
return (
@@ -61,123 +69,84 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
-
-
+
-
+
- {setHeaderActionMenu && (
-
-
-
-
-
-
-
-
-
-
- {ADD_DATA_LABEL}
-
-
-
-
- )}
-
-
-
-
-
- {/** !! Need to be kept in sync with the deepLinks in x-pack/plugins/infra/public/plugin.ts */}
-
+ {setHeaderActionMenu && (
+
+
+
+
+ {settingsTabTitle}
+
+
+
+
+
+
+
+
+
+
+ {ADD_DATA_LABEL}
+
-
+
+ )}
-
-
- (
-
- {({ configuration, createDerivedIndexPattern }) => (
-
-
- {configuration ? (
-
- ) : (
-
- )}
-
- )}
-
- )}
- />
-
-
-
+
+
+
+ (
+
+ {({ configuration, createDerivedIndexPattern }) => (
+
+
+ {configuration ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+ )}
+ />
+
+
+
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx
index 6176dce9a8782..fe0fbeecf8408 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx
@@ -74,8 +74,8 @@ const BottomActionContainer = euiStyled.div<{ isOpen: boolean; outerWidth: numbe
right: 0;
transition: transform ${TRANSITION_MS}ms;
transform: translateY(${(props) => (props.isOpen ? 0 : '224px')});
- width: ${(props) => props.outerWidth}px;
-`;
+ width: ${(props) => props.outerWidth + 34}px;
+`; // Additional width comes from the padding on the EuiPageBody and inner nodes container
const BottomActionTopBar = euiStyled(EuiFlexGroup).attrs({
justifyContent: 'spaceBetween',
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/filter_bar.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/filter_bar.tsx
index 145302b6cf8eb..7cb75e488583a 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/filter_bar.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/filter_bar.tsx
@@ -10,17 +10,19 @@ import React from 'react';
import { WaffleTimeControls } from './waffle/waffle_time_controls';
import { SearchBar } from './search_bar';
-import { ToolbarPanel } from '../../../../components/toolbar_panel';
export const FilterBar = () => (
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
);
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx
index 88863121f4af5..993ae1d9d7571 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx
@@ -16,7 +16,7 @@ import { EuiButtonEmpty } from '@elastic/eui';
import moment from 'moment';
import { EuiTabs } from '@elastic/eui';
import { EuiTab } from '@elastic/eui';
-import { SubscriptionSplashContent } from '../../../../../../components/subscription_splash_content';
+import { SubscriptionSplashPrompt } from '../../../../../../components/subscription_splash_content';
import { useInfraMLCapabilitiesContext } from '../../../../../../containers/ml/infra_ml_capabilities';
import {
MissingResultsPrivilegesPrompt,
@@ -24,7 +24,7 @@ import {
} from '../../../../../../components/logging/log_analysis_setup';
import { useMetricHostsModuleContext } from '../../../../../../containers/ml/modules/metrics_hosts/module';
import { useMetricK8sModuleContext } from '../../../../../../containers/ml/modules/metrics_k8s/module';
-import { LoadingPage } from '../../../../../../components/loading_page';
+import { LoadingPrompt } from '../../../../../../components/loading_page';
import { useLinkProps } from '../../../../../../hooks/use_link_props';
import { AnomaliesTable } from './anomalies_table/anomalies_table';
@@ -81,12 +81,12 @@ export const FlyoutHome = (props: Props) => {
});
if (!hasInfraMLCapabilities) {
- return ;
+ return ;
} else if (!hasInfraMLReadCapabilities) {
return ;
} else if (hostSetupStatus.type === 'initializing' || k8sSetupStatus.type === 'initializing') {
return (
- {
);
return (
-
-
+
+
{
const uiCapabilities = useKibana().services.application?.capabilities;
@@ -48,75 +54,94 @@ export const SnapshotPage = () => {
return (
-
-
- i18n.translate('xpack.infra.infrastructureSnapshotPage.documentTitle', {
- defaultMessage: '{previousTitle} | Inventory',
- values: {
- previousTitle,
- },
- })
- }
- />
- {isLoading && !source ? (
-
- ) : metricIndicesExist ? (
- <>
-
-
+ i18n.translate('xpack.infra.infrastructureSnapshotPage.documentTitle', {
+ defaultMessage: '{previousTitle} | Inventory',
+ values: {
+ previousTitle,
+ },
+ })
+ }
+ />
+ {isLoading && !source ? (
+
+ ) : metricIndicesExist ? (
+ <>
+
+
-
-
- >
- ) : hasFailedLoadingSource ? (
-
- ) : (
-
+
+
+
+
+
+
+ >
+ ) : hasFailedLoadingSource ? (
+
+ ) : (
+
+
+
+ {i18n.translate('xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel', {
+ defaultMessage: 'View setup instructions',
+ })}
+
+
+ {uiCapabilities?.infrastructure?.configureSource ? (
-
- {i18n.translate(
- 'xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel',
- {
- defaultMessage: 'View setup instructions',
- }
- )}
-
+ {i18n.translate('xpack.infra.configureSourceActionLabel', {
+ defaultMessage: 'Change source configuration',
+ })}
+
- {uiCapabilities?.infrastructure?.configureSource ? (
-
-
- {i18n.translate('xpack.infra.configureSourceActionLabel', {
- defaultMessage: 'Change source configuration',
- })}
-
-
- ) : null}
-
- }
- data-test-subj="noMetricsIndicesPrompt"
- />
- )}
-
+ ) : null}
+
+ }
+ data-test-subj="noMetricsIndicesPrompt"
+ />
+ )}
);
};
+
+// This is added to facilitate a full height layout whereby the
+// inner container will set it's own height and be scrollable.
+// The "fullHeight" prop won't help us as it only applies to certain breakpoints.
+export const InventoryPageWrapper = euiStyled.div`
+ .euiPage .euiPageContentBody {
+ display: flex;
+ flex-direction: column;
+ flex: 1 0 auto;
+ width: 100%;
+ height: 100%;
+ }
+`;
diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/node_details_page.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/node_details_page.tsx
index 631ec676f70f5..24b58a4c9a44e 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/node_details_page.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/node_details_page.tsx
@@ -6,27 +6,19 @@
*/
import React, { useCallback, useEffect } from 'react';
-import {
- EuiPage,
- EuiPageBody,
- EuiPageHeader,
- EuiPageHeaderSection,
- EuiHideFor,
- EuiTitle,
-} from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { InfraTimerangeInput } from '../../../../../common/http_api/snapshot_api';
import { InventoryMetric, InventoryItemType } from '../../../../../common/inventory_models/types';
import { useNodeDetails } from '../hooks/use_node_details';
import { MetricsSideNav } from './side_nav';
-import { AutoSizer } from '../../../../components/auto_sizer';
import { MetricsTimeControls } from './time_controls';
import { SideNavContext, NavItem } from '../lib/side_nav_context';
import { PageBody } from './page_body';
-import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common';
import { MetricsTimeInput } from '../hooks/use_metrics_time';
import { InfraMetadata } from '../../../../../common/http_api/metadata_api';
import { PageError } from './page_error';
import { MetadataContext } from '../containers/metadata_context';
+import { MetricsPageTemplate } from '../../page_template';
interface Props {
name: string;
@@ -72,69 +64,47 @@ export const NodeDetailsPage = (props: Props) => {
}
return (
-
-
-
- {({ bounds: { width = 0 } }) => {
- const w = width ? `${width}px` : `100%`;
- return (
-
-
-
-
-
-
-
- {props.name}
-
-
-
-
-
-
-
-
- 0 && props.isAutoReloading ? false : loading}
- refetch={refetch}
- type={props.nodeType}
- metrics={metrics}
- onChangeRangeTime={props.setTimeRange}
- isLiveStreaming={props.isAutoReloading}
- stopLiveStreaming={() => props.setAutoReload(false)}
- />
-
-
-
-
- );
- }}
-
-
+ ,
+ ],
+ }}
+ >
+
+
+
+
+
+
+
+ 0 && props.isAutoReloading ? false : loading}
+ refetch={refetch}
+ type={props.nodeType}
+ metrics={metrics}
+ onChangeRangeTime={props.setTimeRange}
+ isLiveStreaming={props.isAutoReloading}
+ stopLiveStreaming={() => props.setAutoReload(false)}
+ />
+
+
+
+
+
);
};
-
-const MetricsDetailsPageColumn = euiStyled.div`
- flex: 1 0 0%;
- display: flex;
- flex-direction: column;
-`;
-
-const MetricsTitleTimeRangeContainer = euiStyled.div`
- display: flex;
- flex-flow: row wrap;
- justify-content: space-between;
-`;
diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/side_nav.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/side_nav.tsx
index 478e85b1b4e27..a0ef8ab40ad90 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/side_nav.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/side_nav.tsx
@@ -7,9 +7,7 @@
import { EuiHideFor, EuiPageSideBar, EuiShowFor, EuiSideNav } from '@elastic/eui';
import React, { useState, useCallback } from 'react';
-import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common';
import { NavItem } from '../lib/side_nav_context';
-
interface Props {
loading: boolean;
name: string;
@@ -32,22 +30,15 @@ export const MetricsSideNav = ({ loading, name, items }: Props) => {
isOpenOnMobile={isOpenOnMobile}
/>
);
+
return (
-
-
- {content}
+ <>
+
+ {content}
- {mobileContent}
-
+
+ {mobileContent}
+
+ >
);
};
-
-const SideNavContainer = euiStyled.div`
- position: fixed;
- z-index: 1;
- height: 88vh;
- padding-left: 16px;
- margin-left: -16px;
- overflow-y: auto;
- overflow-x: hidden;
-`;
diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/index.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/index.tsx
index 13fa5cf1f0667..a447989530727 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/index.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/index.tsx
@@ -7,14 +7,9 @@
import { i18n } from '@kbn/i18n';
import React, { useContext, useState } from 'react';
-import {
- euiStyled,
- EuiTheme,
- withTheme,
-} from '../../../../../../../src/plugins/kibana_react/common';
+import { EuiTheme, withTheme } from '../../../../../../../src/plugins/kibana_react/common';
import { DocumentTitle } from '../../../components/document_title';
import { Header } from '../../../components/header';
-import { ColumnarPage, PageContent } from '../../../components/page';
import { withMetricPageProviders } from './page_providers';
import { useMetadata } from './hooks/use_metadata';
import { Source } from '../../../containers/metrics_source';
@@ -26,11 +21,7 @@ import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'
import { InventoryItemType } from '../../../../common/inventory_models/types';
import { useMetricsTimeContext } from './hooks/use_metrics_time';
import { useLinkProps } from '../../../hooks/use_link_props';
-
-const DetailPageContent = euiStyled(PageContent)`
- overflow: auto;
- background-color: ${(props) => props.theme.eui.euiColorLightestShade};
-`;
+import { MetricsPageTemplate } from '../page_template';
interface Props {
theme: EuiTheme | undefined;
@@ -49,6 +40,7 @@ export const MetricDetail = withMetricPageProviders(
const nodeType = match.params.type as InventoryItemType;
const inventoryModel = findInventoryModel(nodeType);
const { sourceId } = useContext(Source.Context);
+
const {
timeRange,
parsedTimeRange,
@@ -95,18 +87,20 @@ export const MetricDetail = withMetricPageProviders(
if (metadataLoading && !filteredRequiredMetrics.length) {
return (
-
+
+
+
);
}
return (
-
+ <>
-
- {metadata ? (
-
- ) : null}
-
-
+ {metadata ? (
+
+ ) : null}
+ >
);
})
);
diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/toolbar.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/toolbar.tsx
index e34cd1fd479b8..1b33546d3b68f 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/toolbar.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/toolbar.tsx
@@ -26,7 +26,6 @@ import { MetricsExplorerChartOptions as MetricsExplorerChartOptionsComponent } f
import { SavedViewsToolbarControls } from '../../../../components/saved_views/toolbar_control';
import { useKibanaUiSetting } from '../../../../utils/use_kibana_ui_setting';
import { mapKibanaQuickRangesToDatePickerRanges } from '../../../../utils/map_timepicker_quickranges_to_datepicker_ranges';
-import { ToolbarPanel } from '../../../../components/toolbar_panel';
interface Props {
derivedIndexPattern: IIndexPattern;
@@ -60,7 +59,7 @@ export const MetricsExplorerToolbar = ({
const commonlyUsedRanges = mapKibanaQuickRangesToDatePickerRanges(timepickerQuickRanges);
return (
-
+ <>
-
+ >
);
};
diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx
index 0d1ac47812577..1ecadcac4e287 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx
@@ -17,12 +17,17 @@ import { MetricsExplorerCharts } from './components/charts';
import { MetricsExplorerToolbar } from './components/toolbar';
import { useMetricsExplorerState } from './hooks/use_metric_explorer_state';
import { useSavedViewContext } from '../../../containers/saved_view/saved_view';
+import { MetricsPageTemplate } from '../page_template';
interface MetricsExplorerPageProps {
source: MetricsSourceConfigurationProperties;
derivedIndexPattern: IIndexPattern;
}
+const metricsExplorerTitle = i18n.translate('xpack.infra.metrics.metricsExplorerTitle', {
+ defaultMessage: 'Metrics Explorer',
+});
+
export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExplorerPageProps) => {
const {
loading,
@@ -73,43 +78,49 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl
})
}
/>
-
- {error ? (
-
- ) : (
-
+
- )}
+ {error ? (
+
+ ) : (
+
+ )}
+
);
};
diff --git a/x-pack/plugins/infra/public/pages/metrics/page_template.tsx b/x-pack/plugins/infra/public/pages/metrics/page_template.tsx
new file mode 100644
index 0000000000000..c32133525e8e8
--- /dev/null
+++ b/x-pack/plugins/infra/public/pages/metrics/page_template.tsx
@@ -0,0 +1,24 @@
+/*
+ * 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 { useKibanaContextForPlugin } from '../../hooks/use_kibana';
+import type { LazyObservabilityPageTemplateProps } from '../../../../observability/public';
+
+export const MetricsPageTemplate: React.FC = (
+ pageTemplateProps
+) => {
+ const {
+ services: {
+ observability: {
+ navigation: { PageTemplate },
+ },
+ },
+ } = useKibanaContextForPlugin();
+
+ return ;
+};
diff --git a/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx b/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx
index 0579194464f4e..1066dddad6b5f 100644
--- a/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx
@@ -10,8 +10,6 @@ import {
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
- EuiPage,
- EuiPageBody,
EuiPanel,
EuiSpacer,
} from '@elastic/eui';
@@ -27,11 +25,15 @@ import { IndicesConfigurationPanel } from './indices_configuration_panel';
import { MLConfigurationPanel } from './ml_configuration_panel';
import { NameConfigurationPanel } from './name_configuration_panel';
import { useSourceConfigurationFormState } from './source_configuration_form_state';
-
+import { MetricsPageTemplate } from '../page_template';
interface SourceConfigurationSettingsProps {
shouldAllowEdit: boolean;
}
+const settingsTitle = i18n.translate('xpack.infra.metrics.settingsTitle', {
+ defaultMessage: 'Settings',
+});
+
export const SourceConfigurationSettings = ({
shouldAllowEdit,
}: SourceConfigurationSettingsProps) => {
@@ -84,126 +86,124 @@ export const SourceConfigurationSettings = ({
}
return (
- <>
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {hasInfraMLCapabilities && (
+ <>
-
- {hasInfraMLCapabilities && (
- <>
-
-
-
-
- >
- )}
- {errors.length > 0 ? (
- <>
-
-
- {errors.map((error, errorIndex) => (
- {error}
- ))}
-
-
-
- >
- ) : null}
+ >
+ )}
+ {errors.length > 0 ? (
+ <>
+
+
+ {errors.map((error, errorIndex) => (
+ {error}
+ ))}
+
+
-
- {isWriteable && (
-
- {isLoading ? (
-
-
-
- Loading
-
-
-
- ) : (
- <>
-
-
- {
- resetForm();
- }}
- >
-
-
-
-
-
-
-
-
-
- >
- )}
-
+ >
+ ) : null}
+
+
+ {isWriteable && (
+
+ {isLoading ? (
+
+
+
+ Loading
+
+
+
+ ) : (
+ <>
+
+
+ {
+ resetForm();
+ }}
+ >
+
+
+
+
+
+
+
+
+
+ >
)}
-
-
-
- >
+
+ )}
+
+
);
};
diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts
index 9948976b01ea1..fd599aed5f890 100644
--- a/x-pack/plugins/infra/public/plugin.ts
+++ b/x-pack/plugins/infra/public/plugin.ts
@@ -7,6 +7,8 @@
import { i18n } from '@kbn/i18n';
import { AppMountParameters, PluginInitializerContext } from 'kibana/public';
+import { from } from 'rxjs';
+import { map } from 'rxjs/operators';
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public';
import { createMetricThresholdAlertType } from './alerting/metric_threshold';
import { createInventoryMetricAlertType } from './alerting/inventory';
@@ -36,19 +38,50 @@ export class Plugin implements InfraClientPluginClass {
pluginsSetup.triggersActionsUi.alertTypeRegistry.register(getLogsAlertType());
pluginsSetup.triggersActionsUi.alertTypeRegistry.register(createMetricThresholdAlertType());
- if (pluginsSetup.observability) {
- pluginsSetup.observability.dashboard.register({
- appName: 'infra_logs',
- hasData: getLogsHasDataFetcher(core.getStartServices),
- fetchData: getLogsOverviewDataFetcher(core.getStartServices),
- });
-
- pluginsSetup.observability.dashboard.register({
- appName: 'infra_metrics',
- hasData: createMetricsHasData(core.getStartServices),
- fetchData: createMetricsFetchData(core.getStartServices),
- });
- }
+ pluginsSetup.observability.dashboard.register({
+ appName: 'infra_logs',
+ hasData: getLogsHasDataFetcher(core.getStartServices),
+ fetchData: getLogsOverviewDataFetcher(core.getStartServices),
+ });
+
+ pluginsSetup.observability.dashboard.register({
+ appName: 'infra_metrics',
+ hasData: createMetricsHasData(core.getStartServices),
+ fetchData: createMetricsFetchData(core.getStartServices),
+ });
+
+ /** !! Need to be kept in sync with the deepLinks in x-pack/plugins/infra/public/plugin.ts */
+ pluginsSetup.observability.navigation.registerSections(
+ from(core.getStartServices()).pipe(
+ map(([{ application: { capabilities } }]) => [
+ ...(capabilities.logs.show
+ ? [
+ {
+ label: 'Logs',
+ sortKey: 200,
+ entries: [
+ { label: 'Stream', app: 'logs', path: '/stream' },
+ { label: 'Anomalies', app: 'logs', path: '/anomalies' },
+ { label: 'Categories', app: 'logs', path: '/log-categories' },
+ ],
+ },
+ ]
+ : []),
+ ...(capabilities.infrastructure.show
+ ? [
+ {
+ label: 'Metrics',
+ sortKey: 300,
+ entries: [
+ { label: 'Inventory', app: 'metrics', path: '/inventory' },
+ { label: 'Metrics Explorer', app: 'metrics', path: '/explorer' },
+ ],
+ },
+ ]
+ : []),
+ ])
+ )
+ );
pluginsSetup.embeddable.registerEmbeddableFactory(
LOG_STREAM_EMBEDDABLE,
diff --git a/x-pack/plugins/observability/public/components/shared/index.tsx b/x-pack/plugins/observability/public/components/shared/index.tsx
index f04ca5b857916..9d557a40b7987 100644
--- a/x-pack/plugins/observability/public/components/shared/index.tsx
+++ b/x-pack/plugins/observability/public/components/shared/index.tsx
@@ -10,6 +10,7 @@ import type { CoreVitalProps, HeaderMenuPortalProps } from './types';
import type { FieldValueSuggestionsProps } from './field_value_suggestions/types';
export { createLazyObservabilityPageTemplate } from './page_template';
+export type { LazyObservabilityPageTemplateProps } from './page_template';
const CoreVitalsLazy = lazy(() => import('./core_web_vitals/index'));
diff --git a/x-pack/plugins/observability/public/components/shared/page_template/index.ts b/x-pack/plugins/observability/public/components/shared/page_template/index.ts
index 14793ba402251..5ecb2c493f687 100644
--- a/x-pack/plugins/observability/public/components/shared/page_template/index.ts
+++ b/x-pack/plugins/observability/public/components/shared/page_template/index.ts
@@ -6,3 +6,4 @@
*/
export { createLazyObservabilityPageTemplate } from './lazy_page_template';
+export type { LazyObservabilityPageTemplateProps } from './lazy_page_template';
diff --git a/x-pack/plugins/observability/public/components/shared/page_template/page_template.tsx b/x-pack/plugins/observability/public/components/shared/page_template/page_template.tsx
index bebcd53f8ae6c..5a7ce3502ce84 100644
--- a/x-pack/plugins/observability/public/components/shared/page_template/page_template.tsx
+++ b/x-pack/plugins/observability/public/components/shared/page_template/page_template.tsx
@@ -28,6 +28,7 @@ export type WrappedPageTemplateProps = Pick<
| 'pageContentProps'
| 'pageHeader'
| 'restrictWidth'
+ | 'isEmptyState'
> &
// recreate the exclusivity of bottomBar-related props
ExclusiveUnion<
diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts
index 030046ce7bed9..6bd9596d5d296 100644
--- a/x-pack/plugins/observability/public/index.ts
+++ b/x-pack/plugins/observability/public/index.ts
@@ -43,6 +43,8 @@ export {
FieldValueSuggestions,
} from './components/shared/';
+export type { LazyObservabilityPageTemplateProps } from './components/shared';
+
export {
useTrackPageview,
useUiTracker,
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index e47dfdf986eee..9be5957cd4718 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -11031,7 +11031,6 @@
"xpack.infra.header.badge.readOnly.text": "読み取り専用",
"xpack.infra.header.badge.readOnly.tooltip": "ソース構成を変更できません",
"xpack.infra.header.infrastructureHelpAppName": "メトリック",
- "xpack.infra.header.infrastructureNavigationTitle": "メトリック",
"xpack.infra.header.infrastructureTitle": "メトリック",
"xpack.infra.header.logsTitle": "ログ",
"xpack.infra.hideHistory": "履歴を表示しない",
@@ -11164,7 +11163,6 @@
"xpack.infra.logs.analysis.anomaliesExpandedRowActualRateDescription": "実際",
"xpack.infra.logs.analysis.anomaliesExpandedRowTypicalRateDescription": "通常",
"xpack.infra.logs.analysis.anomaliesSectionLoadingAriaLabel": "異常を読み込み中",
- "xpack.infra.logs.analysis.anomaliesSectionTitle": "異常",
"xpack.infra.logs.analysis.anomaliesTableAnomalyDatasetName": "データセット",
"xpack.infra.logs.analysis.anomaliesTableAnomalyMessageName": "異常",
"xpack.infra.logs.analysis.anomaliesTableAnomalyScoreColumnName": "異常スコア",
@@ -11254,7 +11252,6 @@
"xpack.infra.logs.logEntryCategories.showAnalysisSetupButtonLabel": "ML設定",
"xpack.infra.logs.logEntryCategories.singleCategoryWarningReasonDescription": "分析では、ログメッセージから2つ以上のカテゴリを抽出できませんでした。",
"xpack.infra.logs.logEntryCategories.topCategoriesSectionLoadingAriaLabel": "メッセージカテゴリーを読み込み中",
- "xpack.infra.logs.logEntryCategories.topCategoriesSectionTitle": "メッセージカテゴリーをログに出力",
"xpack.infra.logs.logEntryCategories.trendColumnTitle": "傾向",
"xpack.infra.logs.logEntryExamples.exampleEmptyDescription": "選択した時間範囲内に例は見つかりませんでした。ログエントリー保持期間を長くするとメッセージサンプルの可用性が向上します。",
"xpack.infra.logs.logEntryExamples.exampleEmptyReloadButtonLabel": "再読み込み",
@@ -11686,10 +11683,7 @@
"xpack.infra.ml.aomalyFlyout.jobSetup.flyoutHeader": "{nodeType}の機械学習を有効にする",
"xpack.infra.ml.metricsHostModuleDescription": "機械学習を使用して自動的に異常ログエントリ率を検出します。",
"xpack.infra.ml.metricsModuleName": "メトリック異常検知",
- "xpack.infra.ml.splash.learnMoreLink": "ドキュメンテーションを表示",
- "xpack.infra.ml.splash.learnMoreTitle": "詳細について",
"xpack.infra.ml.splash.loadingMessage": "ライセンスを確認しています...",
- "xpack.infra.ml.splash.splashImageAlt": "プレースホルダー画像",
"xpack.infra.ml.splash.startTrialCta": "トライアルを開始",
"xpack.infra.ml.splash.startTrialDescription": "無料の試用版には、機械学習機能が含まれており、ログで異常を検出することができます。",
"xpack.infra.ml.splash.startTrialTitle": "異常検知を利用するには、無料の試用版を開始してください",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 3ab6203b48942..d6ebf8a32bcc4 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -11172,7 +11172,6 @@
"xpack.infra.header.badge.readOnly.text": "只读",
"xpack.infra.header.badge.readOnly.tooltip": "无法更改源配置",
"xpack.infra.header.infrastructureHelpAppName": "指标",
- "xpack.infra.header.infrastructureNavigationTitle": "指标",
"xpack.infra.header.infrastructureTitle": "指标",
"xpack.infra.header.logsTitle": "日志",
"xpack.infra.hideHistory": "隐藏历史记录",
@@ -11307,7 +11306,6 @@
"xpack.infra.logs.analysis.anomaliesExpandedRowTypicalRateDescription": "典型",
"xpack.infra.logs.analysis.anomaliesExpandedRowTypicalRateTitle": "{typicalCount, plural, other {消息}}",
"xpack.infra.logs.analysis.anomaliesSectionLoadingAriaLabel": "正在加载异常",
- "xpack.infra.logs.analysis.anomaliesSectionTitle": "异常",
"xpack.infra.logs.analysis.anomaliesTableAnomalyDatasetName": "数据集",
"xpack.infra.logs.analysis.anomaliesTableAnomalyMessageName": "异常",
"xpack.infra.logs.analysis.anomaliesTableAnomalyScoreColumnName": "异常分数",
@@ -11406,7 +11404,6 @@
"xpack.infra.logs.logEntryCategories.showAnalysisSetupButtonLabel": "ML 设置",
"xpack.infra.logs.logEntryCategories.singleCategoryWarningReasonDescription": "分析无法从日志消息中提取多个类别。",
"xpack.infra.logs.logEntryCategories.topCategoriesSectionLoadingAriaLabel": "正在加载消息类别",
- "xpack.infra.logs.logEntryCategories.topCategoriesSectionTitle": "日志消息类别",
"xpack.infra.logs.logEntryCategories.trendColumnTitle": "趋势",
"xpack.infra.logs.logEntryCategories.truncatedPatternSegmentDescription": "{extraSegmentCount, plural, one {另一个分段} other {另 # 个分段}}",
"xpack.infra.logs.logEntryExamples.exampleEmptyDescription": "选定时间范围内未找到任何示例。增大日志条目保留期限以改善消息样例可用性。",
@@ -11845,10 +11842,7 @@
"xpack.infra.ml.aomalyFlyout.jobSetup.flyoutHeader": "为 {nodeType} 启用 Machine Learning",
"xpack.infra.ml.metricsHostModuleDescription": "使用 Machine Learning 自动检测异常日志条目速率。",
"xpack.infra.ml.metricsModuleName": "指标异常检测",
- "xpack.infra.ml.splash.learnMoreLink": "阅读文档",
- "xpack.infra.ml.splash.learnMoreTitle": "希望了解详情?",
"xpack.infra.ml.splash.loadingMessage": "正在检查许可证......",
- "xpack.infra.ml.splash.splashImageAlt": "占位符图像",
"xpack.infra.ml.splash.startTrialCta": "开始试用",
"xpack.infra.ml.splash.startTrialDescription": "我们的免费试用版包含 Machine Learning 功能,可用于检测日志中的异常。",
"xpack.infra.ml.splash.startTrialTitle": "要访问异常检测,请启动免费试用版",
diff --git a/x-pack/test/functional/page_objects/infra_home_page.ts b/x-pack/test/functional/page_objects/infra_home_page.ts
index 8dfef36c3a1c1..fe4a296bae0b7 100644
--- a/x-pack/test/functional/page_objects/infra_home_page.ts
+++ b/x-pack/test/functional/page_objects/infra_home_page.ts
@@ -10,11 +10,12 @@ import testSubjSelector from '@kbn/test-subj-selector';
import { FtrProviderContext } from '../ftr_provider_context';
-export function InfraHomePageProvider({ getService }: FtrProviderContext) {
+export function InfraHomePageProvider({ getService, getPageObjects }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const retry = getService('retry');
const find = getService('find');
const browser = getService('browser');
+ const pageObjects = getPageObjects(['common']);
return {
async goToTime(time: string) {
@@ -87,15 +88,30 @@ export function InfraHomePageProvider({ getService }: FtrProviderContext) {
},
async goToSettings() {
- await testSubjects.click('infrastructureNavLink_/settings');
+ await pageObjects.common.navigateToUrlWithBrowserHistory(
+ 'infraOps',
+ `/settings`,
+ undefined,
+ { ensureCurrentUrl: false } // Test runner struggles with `rison-node` escaped values
+ );
},
async goToInventory() {
- await testSubjects.click('infrastructureNavLink_/inventory');
+ await pageObjects.common.navigateToUrlWithBrowserHistory(
+ 'infraOps',
+ `/inventory`,
+ undefined,
+ { ensureCurrentUrl: false } // Test runner struggles with `rison-node` escaped values
+ );
},
async goToMetricExplorer() {
- return await testSubjects.click('infrastructureNavLink_/infrastructure/metrics-explorer');
+ await pageObjects.common.navigateToUrlWithBrowserHistory(
+ 'infraOps',
+ `/explorer`,
+ undefined,
+ { ensureCurrentUrl: false } // Test runner struggles with `rison-node` escaped values
+ );
},
async getSaveViewButton() {
From df89ecee1b326282f10a194d01b436f172cab4e7 Mon Sep 17 00:00:00 2001
From: Brian Seeders
Date: Mon, 14 Jun 2021 14:54:18 -0400
Subject: [PATCH 17/91] [FTSR] Add pluginFunctional to test suites (#102113)
---
.ci/Jenkinsfile_flaky | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.ci/Jenkinsfile_flaky b/.ci/Jenkinsfile_flaky
index 8121405e5ae24..370643789c2cd 100644
--- a/.ci/Jenkinsfile_flaky
+++ b/.ci/Jenkinsfile_flaky
@@ -4,7 +4,6 @@ library 'kibana-pipeline-library'
kibanaLibrary.load()
def TASK_PARAM = params.TASK ?: params.CI_GROUP
-
// Looks like 'oss:ciGroup:1', 'oss:firefoxSmoke'
def JOB_PARTS = TASK_PARAM.split(':')
def IS_XPACK = JOB_PARTS[0] == 'xpack'
@@ -111,6 +110,8 @@ def getWorkerFromParams(isXpack, job, ciGroup) {
return kibanaPipeline.scriptTaskDocker('Jest Integration Tests', 'test/scripts/test/jest_integration.sh')
} else if (job == 'apiIntegration') {
return kibanaPipeline.scriptTask('API Integration Tests', 'test/scripts/test/api_integration.sh')
+ } else if (job == 'pluginFunctional') {
+ return kibanaPipeline.functionalTestProcess('oss-pluginFunctional', './test/scripts/jenkins_plugin_functional.sh')
} else {
return kibanaPipeline.ossCiGroupProcess(ciGroup)
}
From 8bdc0d7b253b18b6fa45e856189ca63643cbc5db Mon Sep 17 00:00:00 2001
From: Domenico Andreoli
Date: Mon, 14 Jun 2021 21:31:57 +0200
Subject: [PATCH 18/91] Fix esArchiver path in the Jenkins context (#102095)
---
.../apps/alerts/alerts_encryption_keys.js | 8 +++++++-
.../apps/ccs/ccs_discover.js | 10 +++++++---
.../apps/metricbeat/_metricbeat_dashboard.js | 9 +++++++--
3 files changed, 21 insertions(+), 6 deletions(-)
diff --git a/x-pack/test/stack_functional_integration/apps/alerts/alerts_encryption_keys.js b/x-pack/test/stack_functional_integration/apps/alerts/alerts_encryption_keys.js
index f11aa7e09635b..e4ac00661c078 100644
--- a/x-pack/test/stack_functional_integration/apps/alerts/alerts_encryption_keys.js
+++ b/x-pack/test/stack_functional_integration/apps/alerts/alerts_encryption_keys.js
@@ -6,8 +6,14 @@
*/
import expect from '@kbn/expect';
+import { resolve } from 'path';
+import { REPO_ROOT } from '@kbn/dev-utils';
-const ARCHIVE = '../integration-test/test/es_archives/email_connectors_with_encryption_rotation';
+const INTEGRATION_TEST_ROOT = process.env.WORKSPACE || resolve(REPO_ROOT, '../integration-test');
+const ARCHIVE = resolve(
+ INTEGRATION_TEST_ROOT,
+ 'test/es_archives/email_connectors_with_encryption_rotation'
+);
export default ({ getPageObjects, getService }) => {
const esArchiver = getService('esArchiver');
diff --git a/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js b/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js
index 00fce236b5d17..a22e4438c7dbd 100644
--- a/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js
+++ b/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js
@@ -6,11 +6,15 @@
*/
import fs from 'fs';
+import { resolve } from 'path';
import expect from '@kbn/expect';
import { Client as EsClient } from '@elastic/elasticsearch';
import { KbnClient } from '@kbn/test';
import { EsArchiver } from '@kbn/es-archiver';
-import { CA_CERT_PATH } from '@kbn/dev-utils';
+import { CA_CERT_PATH, REPO_ROOT } from '@kbn/dev-utils';
+
+const INTEGRATION_TEST_ROOT = process.env.WORKSPACE || resolve(REPO_ROOT, '../integration-test');
+const ARCHIVE = resolve(INTEGRATION_TEST_ROOT, 'test/es_archives/metricbeat');
export default ({ getService, getPageObjects }) => {
describe('Cross cluster search test in discover', async () => {
@@ -261,7 +265,7 @@ export default ({ getService, getPageObjects }) => {
before('Prepare data:metricbeat-*', async function () {
log.info('Create index');
- await esArchiver.load('../integration-test/test/es_archives/metricbeat');
+ await esArchiver.load(ARCHIVE);
log.info('Create index pattern');
dataId = await supertest
@@ -321,7 +325,7 @@ export default ({ getService, getPageObjects }) => {
}
log.info('Delete index');
- await esArchiver.unload('../integration-test/test/es_archives/metricbeat');
+ await esArchiver.unload(ARCHIVE);
});
after('Clean up .siem-signal-*', async function () {
diff --git a/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js b/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js
index 2b25d5ffea6e1..b678e88bcf0df 100644
--- a/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js
+++ b/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js
@@ -6,6 +6,11 @@
*/
import expect from '@kbn/expect';
+import { resolve } from 'path';
+import { REPO_ROOT } from '@kbn/dev-utils';
+
+const INTEGRATION_TEST_ROOT = process.env.WORKSPACE || resolve(REPO_ROOT, '../integration-test');
+const ARCHIVE = resolve(INTEGRATION_TEST_ROOT, 'test/es_archives/metricbeat');
export default function ({ getService, getPageObjects, updateBaselines }) {
const screenshot = getService('screenshots');
@@ -15,7 +20,7 @@ export default function ({ getService, getPageObjects, updateBaselines }) {
describe('check metricbeat Dashboard', function () {
before(async function () {
- await esArchiver.load('../integration-test/test/es_archives/metricbeat');
+ await esArchiver.load(ARCHIVE);
// this navigateToActualURL takes the place of navigating to the dashboard landing page,
// filtering on the dashboard name, selecting it, setting the timepicker, and going to full screen
@@ -45,7 +50,7 @@ export default function ({ getService, getPageObjects, updateBaselines }) {
});
after(async function () {
- await esArchiver.unload('../integration-test/test/es_archives/metricbeat');
+ await esArchiver.unload(ARCHIVE);
});
it('[Metricbeat System] Overview ECS should match snapshot', async function () {
From 35f962526597ca7b4592b5d30b3566d770f06f41 Mon Sep 17 00:00:00 2001
From: Apoorva Joshi <30438249+ajosh0504@users.noreply.github.com>
Date: Mon, 14 Jun 2021 12:36:08 -0700
Subject: [PATCH 19/91] [ML] Adds Authentication module with six ML jobs for
ECS data (Auditbeat, Winlogbeat, Filebeat and Logs) (#101840)
* Adding Security Authentication jobs in 7.14
* Renamed some jobs
* Changing memory limits and linting change
* Linting fix
* Changed the order
* Adding module to ml_modules.tsx
* Update recognize_module.ts
this test modules uses older Auditbeat data which predates the event.category field so the test has to be skipped per https://elastic.zoom.us/j/93000943632?pwd=TmpvNWhtYUNzMUc0c0N6Tlc2QlVPZz09
* Update recognize_module.ts
needs to be a single line
* Update recognize_module.ts
Some linters want spaces and some linters want no spaces. This linter wants spaces.
* descriptions
added description text
* Update auth_rare_hour_for_a_user.json
removed a wayward newline char
* Minor nitpicking
* memory limits
raised memory limits to 128mb which is larger than the highest observed peak model bytes for the most memory hungry jobs in this event class.
Co-authored-by: Craig
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../modules/security_auth/logo.json | 3 +
.../modules/security_auth/manifest.json | 77 +++++++++++++++++++
.../ml/auth_high_count_logon_events.json | 29 +++++++
...gh_count_logon_events_for_a_source_ip.json | 34 ++++++++
.../ml/auth_high_count_logon_fails.json | 29 +++++++
.../ml/auth_rare_hour_for_a_user.json | 33 ++++++++
.../ml/auth_rare_source_ip_for_a_user.json | 34 ++++++++
.../security_auth/ml/auth_rare_user.json | 33 ++++++++
...datafeed_auth_high_count_logon_events.json | 26 +++++++
...gh_count_logon_events_for_a_source_ip.json | 26 +++++++
.../datafeed_auth_high_count_logon_fails.json | 26 +++++++
.../datafeed_auth_rare_hour_for_a_user.json | 26 +++++++
...tafeed_auth_rare_source_ip_for_a_user.json | 26 +++++++
.../ml/datafeed_auth_rare_user.json | 26 +++++++
.../components/ml_popover/ml_modules.tsx | 1 +
.../apis/ml/modules/get_module.ts | 1 +
.../apis/ml/modules/recognize_module.ts | 5 +-
17 files changed, 433 insertions(+), 2 deletions(-)
create mode 100755 x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/logo.json
create mode 100755 x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/manifest.json
create mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events.json
create mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events_for_a_source_ip.json
create mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_fails.json
create mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_hour_for_a_user.json
create mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_source_ip_for_a_user.json
create mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_user.json
create mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_high_count_logon_events.json
create mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_high_count_logon_events_for_a_source_ip.json
create mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_high_count_logon_fails.json
create mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_rare_hour_for_a_user.json
create mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_rare_source_ip_for_a_user.json
create mode 100644 x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_rare_user.json
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/logo.json
new file mode 100755
index 0000000000000..862f970b7405d
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/logo.json
@@ -0,0 +1,3 @@
+{
+ "icon": "logoSecurity"
+}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/manifest.json
new file mode 100755
index 0000000000000..480f49f3f2b19
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/manifest.json
@@ -0,0 +1,77 @@
+{
+ "id": "security_auth",
+ "title": "Security: Authentication",
+ "description": "Detect anomalous activity in your ECS-compatible authentication logs.",
+ "type": "auth data",
+ "logoFile": "logo.json",
+ "defaultIndexPattern": "auditbeat-*,logs-*,filebeat-*,winlogbeat-*",
+ "query": {
+ "bool": {
+ "filter": [
+ {
+ "term": {
+ "event.category": "authentication"
+ }
+ }
+ ]
+ }
+ },
+ "jobs": [
+ {
+ "id": "auth_high_count_logon_events_for_a_source_ip",
+ "file": "auth_high_count_logon_events_for_a_source_ip.json"
+ },
+ {
+ "id": "auth_high_count_logon_fails",
+ "file": "auth_high_count_logon_fails.json"
+ },
+ {
+ "id": "auth_high_count_logon_events",
+ "file": "auth_high_count_logon_events.json"
+ },
+ {
+ "id": "auth_rare_hour_for_a_user",
+ "file": "auth_rare_hour_for_a_user.json"
+ },
+ {
+ "id": "auth_rare_source_ip_for_a_user",
+ "file": "auth_rare_source_ip_for_a_user.json"
+ },
+ {
+ "id": "auth_rare_user",
+ "file": "auth_rare_user.json"
+ }
+ ],
+ "datafeeds": [
+ {
+ "id": "datafeed-auth_high_count_logon_events_for_a_source_ip",
+ "file": "datafeed_auth_high_count_logon_events_for_a_source_ip.json",
+ "job_id": "auth_high_count_logon_events_for_a_source_ip"
+ },
+ {
+ "id": "datafeed-auth_high_count_logon_fails",
+ "file": "datafeed_auth_high_count_logon_fails.json",
+ "job_id": "auth_high_count_logon_fails"
+ },
+ {
+ "id": "datafeed-auth_high_count_logon_events",
+ "file": "datafeed_auth_high_count_logon_events.json",
+ "job_id": "auth_high_count_logon_events"
+ },
+ {
+ "id": "datafeed-auth_rare_hour_for_a_user",
+ "file": "datafeed_auth_rare_hour_for_a_user.json",
+ "job_id": "auth_rare_hour_for_a_user"
+ },
+ {
+ "id": "datafeed-auth_rare_source_ip_for_a_user",
+ "file": "datafeed_auth_rare_source_ip_for_a_user.json",
+ "job_id": "auth_rare_source_ip_for_a_user"
+ },
+ {
+ "id": "datafeed-auth_rare_user",
+ "file": "datafeed_auth_rare_user.json",
+ "job_id": "auth_rare_user"
+ }
+ ]
+}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events.json
new file mode 100644
index 0000000000000..ee84fb222bb5c
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events.json
@@ -0,0 +1,29 @@
+{
+ "job_type": "anomaly_detector",
+ "description": "Security: Authentication - looks for an unusually large spike in successful authentication events. This can be due to password spraying, user enumeration or brute force activity.",
+ "groups": [
+ "security",
+ "authentication"
+ ],
+ "analysis_config": {
+ "bucket_span": "15m",
+ "detectors": [
+ {
+ "detector_description": "high count of logon events",
+ "function": "high_non_zero_count",
+ "detector_index": 0
+ }
+ ],
+ "influencers": []
+ },
+ "allow_lazy_open": true,
+ "analysis_limits": {
+ "model_memory_limit": "128mb"
+ },
+ "data_description": {
+ "time_field": "@timestamp"
+ },
+ "custom_settings": {
+ "created_by": "ml-module-security-auth"
+ }
+}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events_for_a_source_ip.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events_for_a_source_ip.json
new file mode 100644
index 0000000000000..7bbbc81b6de7a
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events_for_a_source_ip.json
@@ -0,0 +1,34 @@
+{
+ "job_type": "anomaly_detector",
+ "description": "Security: Authentication - looks for an unusually large spike in successful authentication events events from a particular source IP address. This can be due to password spraying, user enumeration or brute force activity.",
+ "groups": [
+ "security",
+ "authentication"
+ ],
+ "analysis_config": {
+ "bucket_span": "15m",
+ "detectors": [
+ {
+ "detector_description": "high count of auth events for a source IP",
+ "function": "high_non_zero_count",
+ "by_field_name": "source.ip",
+ "detector_index": 0
+ }
+ ],
+ "influencers": [
+ "source.ip",
+ "winlog.event_data.LogonType",
+ "user.name"
+ ]
+ },
+ "allow_lazy_open": true,
+ "analysis_limits": {
+ "model_memory_limit": "128mb"
+ },
+ "data_description": {
+ "time_field": "@timestamp"
+ },
+ "custom_settings": {
+ "created_by": "ml-module-security-auth"
+ }
+}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_fails.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_fails.json
new file mode 100644
index 0000000000000..4b7094e92c6ec
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_fails.json
@@ -0,0 +1,29 @@
+{
+ "job_type": "anomaly_detector",
+ "description": "Security: Authentication - looks for an unusually large spike in authentication failure events. This can be due to password spraying, user enumeration or brute force activity and may be a precursor to account takeover or credentialed access.",
+ "groups": [
+ "security",
+ "authentication"
+ ],
+ "analysis_config": {
+ "bucket_span": "15m",
+ "detectors": [
+ {
+ "detector_description": "high count of logon fails",
+ "function": "high_non_zero_count",
+ "detector_index": 0
+ }
+ ],
+ "influencers": []
+ },
+ "allow_lazy_open": true,
+ "analysis_limits": {
+ "model_memory_limit": "128mb"
+ },
+ "data_description": {
+ "time_field": "@timestamp"
+ },
+ "custom_settings": {
+ "created_by": "ml-module-security-auth"
+ }
+}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_hour_for_a_user.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_hour_for_a_user.json
new file mode 100644
index 0000000000000..bb86d256e59df
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_hour_for_a_user.json
@@ -0,0 +1,33 @@
+{
+ "job_type": "anomaly_detector",
+ "description": "Security: Authentication - looks for a user logging in at a time of day that is unusual for the user. This can be due to credentialed access via a compromised account when the user and the threat actor are in different time zones. In addition, unauthorized user activity often takes place during non-business hours.",
+ "groups": [
+ "security",
+ "authentication"
+ ],
+ "analysis_config": {
+ "bucket_span": "15m",
+ "detectors": [
+ {
+ "detector_description": "rare hour for a user",
+ "function": "time_of_day",
+ "by_field_name": "user.name",
+ "detector_index": 0
+ }
+ ],
+ "influencers": [
+ "source.ip",
+ "user.name"
+ ]
+ },
+ "allow_lazy_open": true,
+ "analysis_limits": {
+ "model_memory_limit": "128mb"
+ },
+ "data_description": {
+ "time_field": "@timestamp"
+ },
+ "custom_settings": {
+ "created_by": "ml-module-security-auth"
+ }
+}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_source_ip_for_a_user.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_source_ip_for_a_user.json
new file mode 100644
index 0000000000000..6f72e148fa38e
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_source_ip_for_a_user.json
@@ -0,0 +1,34 @@
+{
+ "job_type": "anomaly_detector",
+ "description": "Security: Authentication - looks for a user logging in from an IP address that is unusual for the user. This can be due to credentialed access via a compromised account when the user and the threat actor are in different locations. An unusual source IP address for a username could also be due to lateral movement when a compromised account is used to pivot between hosts.",
+ "groups": [
+ "security",
+ "authentication"
+ ],
+ "analysis_config": {
+ "bucket_span": "15m",
+ "detectors": [
+ {
+ "detector_description": "rare source IP for a user",
+ "function": "rare",
+ "by_field_name": "source.ip",
+ "partition_field_name": "user.name",
+ "detector_index": 0
+ }
+ ],
+ "influencers": [
+ "source.ip",
+ "user.name"
+ ]
+ },
+ "allow_lazy_open": true,
+ "analysis_limits": {
+ "model_memory_limit": "128mb"
+ },
+ "data_description": {
+ "time_field": "@timestamp"
+ },
+ "custom_settings": {
+ "created_by": "ml-module-security-auth"
+ }
+}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_user.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_user.json
new file mode 100644
index 0000000000000..5cb9c7112b29d
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_rare_user.json
@@ -0,0 +1,33 @@
+{
+ "job_type": "anomaly_detector",
+ "description": "Security: Authentication - looks for an unusual user name in the authentication logs. An unusual user name is one way of detecting credentialed access by means of a new or dormant user account. A user account that is normally inactive, because the user has left the organization, which becomes active, may be due to credentialed access using a compromised account password. Threat actors will sometimes also create new users as a means of persisting in a compromised web application.",
+ "groups": [
+ "security",
+ "authentication"
+ ],
+ "analysis_config": {
+ "bucket_span": "15m",
+ "detectors": [
+ {
+ "detector_description": "rare user",
+ "function": "rare",
+ "by_field_name": "user.name",
+ "detector_index": 0
+ }
+ ],
+ "influencers": [
+ "source.ip",
+ "user.name"
+ ]
+ },
+ "allow_lazy_open": true,
+ "analysis_limits": {
+ "model_memory_limit": "128mb"
+ },
+ "data_description": {
+ "time_field": "@timestamp"
+ },
+ "custom_settings": {
+ "created_by": "ml-module-security-auth"
+ }
+}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_high_count_logon_events.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_high_count_logon_events.json
new file mode 100644
index 0000000000000..eb81179e44363
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_high_count_logon_events.json
@@ -0,0 +1,26 @@
+{
+ "job_id": "auth_high_count_logon_events",
+ "indices": [
+ "auditbeat-*",
+ "logs-*",
+ "filebeat-*",
+ "winlogbeat-*"
+ ],
+ "max_empty_searches": 10,
+ "query": {
+ "bool": {
+ "filter": [
+ {
+ "term": {
+ "event.category": "authentication"
+ }
+ },
+ {
+ "term": {
+ "event.outcome": "success"
+ }
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_high_count_logon_events_for_a_source_ip.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_high_count_logon_events_for_a_source_ip.json
new file mode 100644
index 0000000000000..dfed3ada1fe0b
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_high_count_logon_events_for_a_source_ip.json
@@ -0,0 +1,26 @@
+{
+ "job_id": "auth_high_count_logon_events_for_a_source_ip",
+ "indices": [
+ "auditbeat-*",
+ "logs-*",
+ "filebeat-*",
+ "winlogbeat-*"
+ ],
+ "max_empty_searches": 10,
+ "query": {
+ "bool": {
+ "filter": [
+ {
+ "term": {
+ "event.category": "authentication"
+ }
+ },
+ {
+ "term": {
+ "event.outcome": "success"
+ }
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_high_count_logon_fails.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_high_count_logon_fails.json
new file mode 100644
index 0000000000000..431c115b34d60
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_high_count_logon_fails.json
@@ -0,0 +1,26 @@
+{
+ "job_id": "auth_high_count_logon_fails",
+ "indices": [
+ "auditbeat-*",
+ "logs-*",
+ "filebeat-*",
+ "winlogbeat-*"
+ ],
+ "max_empty_searches": 10,
+ "query": {
+ "bool": {
+ "filter": [
+ {
+ "term": {
+ "event.category": "authentication"
+ }
+ },
+ {
+ "term": {
+ "event.outcome": "failure"
+ }
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_rare_hour_for_a_user.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_rare_hour_for_a_user.json
new file mode 100644
index 0000000000000..377197231f28c
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_rare_hour_for_a_user.json
@@ -0,0 +1,26 @@
+{
+ "job_id": "auth_rare_hour_for_a_user",
+ "indices": [
+ "auditbeat-*",
+ "logs-*",
+ "filebeat-*",
+ "winlogbeat-*"
+ ],
+ "max_empty_searches": 10,
+ "query": {
+ "bool": {
+ "filter": [
+ {
+ "term": {
+ "event.category": "authentication"
+ }
+ },
+ {
+ "term": {
+ "event.outcome": "success"
+ }
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_rare_source_ip_for_a_user.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_rare_source_ip_for_a_user.json
new file mode 100644
index 0000000000000..dfa2ad7ab397c
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_rare_source_ip_for_a_user.json
@@ -0,0 +1,26 @@
+{
+ "job_id": "auth_rare_source_ip_for_a_user",
+ "indices": [
+ "auditbeat-*",
+ "logs-*",
+ "filebeat-*",
+ "winlogbeat-*"
+ ],
+ "max_empty_searches": 10,
+ "query": {
+ "bool": {
+ "filter": [
+ {
+ "term": {
+ "event.category": "authentication"
+ }
+ },
+ {
+ "term": {
+ "event.outcome": "success"
+ }
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_rare_user.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_rare_user.json
new file mode 100644
index 0000000000000..f7de5d3aee71a
--- /dev/null
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/datafeed_auth_rare_user.json
@@ -0,0 +1,26 @@
+{
+ "job_id": "auth_rare_user",
+ "indices": [
+ "auditbeat-*",
+ "logs-*",
+ "filebeat-*",
+ "winlogbeat-*"
+ ],
+ "max_empty_searches": 10,
+ "query": {
+ "bool": {
+ "filter": [
+ {
+ "term": {
+ "event.category": "authentication"
+ }
+ },
+ {
+ "term": {
+ "event.outcome": "success"
+ }
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_modules.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_modules.tsx
index 8dac6234f19a8..e7199f6df2b1f 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_modules.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_modules.tsx
@@ -17,6 +17,7 @@ export const mlModules: string[] = [
'siem_packetbeat',
'siem_winlogbeat',
'siem_winlogbeat_auth',
+ 'security_auth',
'security_linux',
'security_network',
'security_windows',
diff --git a/x-pack/test/api_integration/apis/ml/modules/get_module.ts b/x-pack/test/api_integration/apis/ml/modules/get_module.ts
index 4fa79b915cc5d..0a3e2dbed570b 100644
--- a/x-pack/test/api_integration/apis/ml/modules/get_module.ts
+++ b/x-pack/test/api_integration/apis/ml/modules/get_module.ts
@@ -30,6 +30,7 @@ const moduleIds = [
'nginx_ecs',
'sample_data_ecommerce',
'sample_data_weblogs',
+ 'security_auth',
'security_linux',
'security_network',
'security_windows',
diff --git a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts
index 2181bea8b4040..2742fbff294c0 100644
--- a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts
+++ b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts
@@ -84,7 +84,7 @@ export default ({ getService }: FtrProviderContext) => {
user: USER.ML_POWERUSER,
expected: {
responseCode: 200,
- moduleIds: ['siem_auditbeat', 'siem_auditbeat_auth'],
+ moduleIds: ['security_auth', 'siem_auditbeat', 'siem_auditbeat_auth'],
},
},
{
@@ -105,6 +105,7 @@ export default ({ getService }: FtrProviderContext) => {
expected: {
responseCode: 200,
moduleIds: [
+ 'security_auth',
'security_network',
'security_windows',
'siem_winlogbeat',
@@ -148,7 +149,7 @@ export default ({ getService }: FtrProviderContext) => {
user: USER.ML_POWERUSER,
expected: {
responseCode: 200,
- moduleIds: ['security_linux', 'security_network', 'security_windows'],
+ moduleIds: ['security_auth', 'security_linux', 'security_network', 'security_windows'],
},
},
{
From 2dbf680e22b6dabcc642e727cf6d81afabd18ea3 Mon Sep 17 00:00:00 2001
From: Peter Pisljar
Date: Mon, 14 Jun 2021 22:37:01 +0200
Subject: [PATCH 20/91] adds support for setting multiple variables to varSet
(#100458)
---
.../common/execution/execution.test.ts | 4 +--
.../specs/tests/var_set.test.ts | 36 +++++++++++++++++--
.../expression_functions/specs/var_set.ts | 10 ++++--
3 files changed, 42 insertions(+), 8 deletions(-)
diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts
index 69687f75f3098..feff425cc48ed 100644
--- a/src/plugins/expressions/common/execution/execution.test.ts
+++ b/src/plugins/expressions/common/execution/execution.test.ts
@@ -834,8 +834,8 @@ describe('Execution', () => {
expect((chain[0].arguments.val[0] as ExpressionAstExpression).chain[0].debug!.args).toEqual(
{
- name: 'foo',
- value: 5,
+ name: ['foo'],
+ value: [5],
}
);
});
diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts
index 0a9f022ce89ca..cdcae61215fa4 100644
--- a/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts
+++ b/src/plugins/expressions/common/expression_functions/specs/tests/var_set.test.ts
@@ -9,6 +9,8 @@
import { functionWrapper } from './utils';
import { variableSet } from '../var_set';
import { ExecutionContext } from '../../../execution/types';
+import { createUnitTestExecutor } from '../../../test_helpers';
+import { first } from 'rxjs/operators';
describe('expression_functions', () => {
describe('var_set', () => {
@@ -32,21 +34,49 @@ describe('expression_functions', () => {
});
it('updates a variable', () => {
- const actual = fn(input, { name: 'test', value: 2 }, context);
+ const actual = fn(input, { name: ['test'], value: [2] }, context);
expect(variables.test).toEqual(2);
expect(actual).toEqual(input);
});
it('sets a new variable', () => {
- const actual = fn(input, { name: 'new', value: 3 }, context);
+ const actual = fn(input, { name: ['new'], value: [3] }, context);
expect(variables.new).toEqual(3);
expect(actual).toEqual(input);
});
it('stores context if value is not set', () => {
- const actual = fn(input, { name: 'test' }, context);
+ const actual = fn(input, { name: ['test'], value: [] }, context);
expect(variables.test).toEqual(input);
expect(actual).toEqual(input);
});
+
+ it('sets multiple variables', () => {
+ const actual = fn(input, { name: ['new1', 'new2', 'new3'], value: [1, , 3] }, context);
+ expect(variables.new1).toEqual(1);
+ expect(variables.new2).toEqual(input);
+ expect(variables.new3).toEqual(3);
+ expect(actual).toEqual(input);
+ });
+
+ describe('running function thru executor', () => {
+ const executor = createUnitTestExecutor();
+ executor.registerFunction(variableSet);
+
+ it('sets the variables', async () => {
+ const vars = {};
+ const result = await executor
+ .run('var_set name=test1 name=test2 value=1', 2, { variables: vars })
+ .pipe(first())
+ .toPromise();
+
+ expect(result).toEqual(2);
+
+ expect(vars).toEqual({
+ test1: 1,
+ test2: 2,
+ });
+ });
+ });
});
});
diff --git a/src/plugins/expressions/common/expression_functions/specs/var_set.ts b/src/plugins/expressions/common/expression_functions/specs/var_set.ts
index 490c7781a01a1..f3ac6a2ab80d4 100644
--- a/src/plugins/expressions/common/expression_functions/specs/var_set.ts
+++ b/src/plugins/expressions/common/expression_functions/specs/var_set.ts
@@ -10,8 +10,8 @@ import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition } from '../types';
interface Arguments {
- name: string;
- value?: any;
+ name: string[];
+ value: any[];
}
export type ExpressionFunctionVarSet = ExpressionFunctionDefinition<
@@ -31,12 +31,14 @@ export const variableSet: ExpressionFunctionVarSet = {
types: ['string'],
aliases: ['_'],
required: true,
+ multi: true,
help: i18n.translate('expressions.functions.varset.name.help', {
defaultMessage: 'Specify the name of the variable.',
}),
},
value: {
aliases: ['val'],
+ multi: true,
help: i18n.translate('expressions.functions.varset.val.help', {
defaultMessage:
'Specify the value for the variable. When unspecified, the input context is used.',
@@ -45,7 +47,9 @@ export const variableSet: ExpressionFunctionVarSet = {
},
fn(input, args, context) {
const variables: Record = context.variables;
- variables[args.name] = args.value === undefined ? input : args.value;
+ args.name.forEach((name, i) => {
+ variables[name] = args.value[i] === undefined ? input : args.value[i];
+ });
return input;
},
};
From 7cee2eefdc820123b6350ec5d33fd723fad7bd79 Mon Sep 17 00:00:00 2001
From: Spencer
Date: Mon, 14 Jun 2021 14:01:22 -0700
Subject: [PATCH 21/91] [kbnArchiver] handle archives which have \r\n (#102118)
Co-authored-by: spalger
---
.../import_export/parse_archive.test.ts | 63 +++++++++++++++++++
.../kbn_client/import_export/parse_archive.ts | 22 +++++++
.../kbn_client/kbn_client_import_export.ts | 15 +----
3 files changed, 86 insertions(+), 14 deletions(-)
create mode 100644 packages/kbn-test/src/kbn_client/import_export/parse_archive.test.ts
create mode 100644 packages/kbn-test/src/kbn_client/import_export/parse_archive.ts
diff --git a/packages/kbn-test/src/kbn_client/import_export/parse_archive.test.ts b/packages/kbn-test/src/kbn_client/import_export/parse_archive.test.ts
new file mode 100644
index 0000000000000..25651a0dd2190
--- /dev/null
+++ b/packages/kbn-test/src/kbn_client/import_export/parse_archive.test.ts
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { parseArchive } from './parse_archive';
+
+jest.mock('fs/promises', () => ({
+ readFile: jest.fn(),
+}));
+
+const mockReadFile = jest.requireMock('fs/promises').readFile;
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
+it('parses archives with \\n', async () => {
+ mockReadFile.mockResolvedValue(
+ `{
+ "foo": "abc"
+ }\n\n{
+ "foo": "xyz"
+ }`
+ );
+
+ const archive = await parseArchive('mock');
+ expect(archive).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "foo": "abc",
+ },
+ Object {
+ "foo": "xyz",
+ },
+ ]
+ `);
+});
+
+it('parses archives with \\r\\n', async () => {
+ mockReadFile.mockResolvedValue(
+ `{
+ "foo": "123"
+ }\r\n\r\n{
+ "foo": "456"
+ }`
+ );
+
+ const archive = await parseArchive('mock');
+ expect(archive).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "foo": "123",
+ },
+ Object {
+ "foo": "456",
+ },
+ ]
+ `);
+});
diff --git a/packages/kbn-test/src/kbn_client/import_export/parse_archive.ts b/packages/kbn-test/src/kbn_client/import_export/parse_archive.ts
new file mode 100644
index 0000000000000..b6b85ba521525
--- /dev/null
+++ b/packages/kbn-test/src/kbn_client/import_export/parse_archive.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import Fs from 'fs/promises';
+
+export interface SavedObject {
+ id: string;
+ type: string;
+ [key: string]: unknown;
+}
+
+export async function parseArchive(path: string): Promise {
+ return (await Fs.readFile(path, 'utf-8'))
+ .split(/\r?\n\r?\n/)
+ .filter((line) => !!line)
+ .map((line) => JSON.parse(line));
+}
diff --git a/packages/kbn-test/src/kbn_client/kbn_client_import_export.ts b/packages/kbn-test/src/kbn_client/kbn_client_import_export.ts
index 88953cdbaed7c..4adae7d1cd031 100644
--- a/packages/kbn-test/src/kbn_client/kbn_client_import_export.ts
+++ b/packages/kbn-test/src/kbn_client/kbn_client_import_export.ts
@@ -16,25 +16,12 @@ import { ToolingLog, isAxiosResponseError, createFailError, REPO_ROOT } from '@k
import { KbnClientRequester, uriencode, ReqOptions } from './kbn_client_requester';
import { KbnClientSavedObjects } from './kbn_client_saved_objects';
+import { parseArchive } from './import_export/parse_archive';
interface ImportApiResponse {
success: boolean;
[key: string]: unknown;
}
-
-interface SavedObject {
- id: string;
- type: string;
- [key: string]: unknown;
-}
-
-async function parseArchive(path: string): Promise {
- return (await Fs.readFile(path, 'utf-8'))
- .split('\n\n')
- .filter((line) => !!line)
- .map((line) => JSON.parse(line));
-}
-
export class KbnClientImportExport {
constructor(
public readonly log: ToolingLog,
From 6e0aed79c3375f426e8e7244f2a60741278fcc38 Mon Sep 17 00:00:00 2001
From: Aaron Caldwell
Date: Mon, 14 Jun 2021 15:51:33 -0600
Subject: [PATCH 22/91] [Maps] Fix geo alerts handling of multi-fields
(#100348)
---
.../entity_by_expression.test.tsx.snap | 171 ++++++++++++++++++
.../expressions/entity_by_expression.test.tsx | 94 ++++++++++
.../expressions/entity_by_expression.tsx | 19 +-
3 files changed, 277 insertions(+), 7 deletions(-)
create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/__snapshots__/entity_by_expression.test.tsx.snap
create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_by_expression.test.tsx
diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/__snapshots__/entity_by_expression.test.tsx.snap b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/__snapshots__/entity_by_expression.test.tsx.snap
new file mode 100644
index 0000000000000..d9dd6ec4a0be5
--- /dev/null
+++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/__snapshots__/entity_by_expression.test.tsx.snap
@@ -0,0 +1,171 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render entity by expression with aggregatable field options for entity 1`] = `
+
+
+
+
+ }
+ value="FlightNum"
+ >
+
+ }
+ closePopover={[Function]}
+ display="block"
+ hasArrow={true}
+ id="popoverForExpression"
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="m"
+ zIndex={8000}
+ >
+
+
+
+
+
+ by
+
+
+
+ FlightNum
+
+
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_by_expression.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_by_expression.test.tsx
new file mode 100644
index 0000000000000..31b89873922c9
--- /dev/null
+++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_by_expression.test.tsx
@@ -0,0 +1,94 @@
+/*
+ * 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 { mount } from 'enzyme';
+import { EntityByExpression, getValidIndexPatternFields } from './entity_by_expression';
+
+const defaultProps = {
+ errors: {
+ index: [],
+ indexId: [],
+ geoField: [],
+ entity: [],
+ dateField: [],
+ boundaryType: [],
+ boundaryIndexTitle: [],
+ boundaryIndexId: [],
+ boundaryGeoField: [],
+ name: ['Name is required.'],
+ interval: [],
+ alertTypeId: [],
+ actionConnectors: [],
+ },
+ entity: 'FlightNum',
+ setAlertParamsEntity: (arg: string) => {},
+ indexFields: [
+ {
+ count: 0,
+ name: 'DestLocation',
+ type: 'geo_point',
+ esTypes: ['geo_point'],
+ scripted: false,
+ searchable: true,
+ aggregatable: true,
+ readFromDocValues: true,
+ },
+ {
+ count: 0,
+ name: 'FlightNum',
+ type: 'string',
+ esTypes: ['keyword'],
+ scripted: false,
+ searchable: true,
+ aggregatable: true,
+ readFromDocValues: true,
+ },
+ {
+ count: 0,
+ name: 'OriginLocation',
+ type: 'geo_point',
+ esTypes: ['geo_point'],
+ scripted: false,
+ searchable: true,
+ aggregatable: true,
+ readFromDocValues: true,
+ },
+ {
+ count: 0,
+ name: 'timestamp',
+ type: 'date',
+ esTypes: ['date'],
+ scripted: false,
+ searchable: true,
+ aggregatable: true,
+ readFromDocValues: true,
+ },
+ ],
+ isInvalid: false,
+};
+
+test('should render entity by expression with aggregatable field options for entity', async () => {
+ const component = mount( );
+ expect(component).toMatchSnapshot();
+});
+//
+
+test('should only use valid index fields', async () => {
+ // Only the string index field should match
+ const indexFields = getValidIndexPatternFields(defaultProps.indexFields);
+ expect(indexFields.length).toEqual(1);
+
+ // Set all agg fields to false, invalidating them for use
+ const invalidIndexFields = defaultProps.indexFields.map((field) => ({
+ ...field,
+ aggregatable: false,
+ }));
+
+ const noIndexFields = getValidIndexPatternFields(invalidIndexFields);
+ expect(noIndexFields.length).toEqual(0);
+});
diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_by_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_by_expression.tsx
index 2df6439ad56f0..a194bd40d9931 100644
--- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_by_expression.tsx
+++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_by_expression.tsx
@@ -22,6 +22,16 @@ interface Props {
isInvalid: boolean;
}
+const ENTITY_TYPES = ['string', 'number', 'ip'];
+export function getValidIndexPatternFields(fields: IFieldType[]): IFieldType[] {
+ return fields.filter((field) => {
+ const isSpecifiedSupportedField = ENTITY_TYPES.includes(field.type);
+ const hasLeadingUnderscore = field.name.startsWith('_');
+ const isAggregatable = !!field.aggregatable;
+ return isSpecifiedSupportedField && isAggregatable && !hasLeadingUnderscore;
+ });
+}
+
export const EntityByExpression: FunctionComponent = ({
errors,
entity,
@@ -29,9 +39,6 @@ export const EntityByExpression: FunctionComponent = ({
indexFields,
isInvalid,
}) => {
- // eslint-disable-next-line react-hooks/exhaustive-deps
- const ENTITY_TYPES = ['string', 'number', 'ip'];
-
const usePrevious = (value: T): T | undefined => {
const ref = useRef();
useEffect(() => {
@@ -48,14 +55,12 @@ export const EntityByExpression: FunctionComponent = ({
});
useEffect(() => {
if (!_.isEqual(oldIndexFields, indexFields)) {
- fields.current.indexFields = indexFields.filter(
- (field: IFieldType) => ENTITY_TYPES.includes(field.type) && !field.name.startsWith('_')
- );
+ fields.current.indexFields = getValidIndexPatternFields(indexFields);
if (!entity && fields.current.indexFields.length) {
setAlertParamsEntity(fields.current.indexFields[0].name);
}
}
- }, [ENTITY_TYPES, indexFields, oldIndexFields, setAlertParamsEntity, entity]);
+ }, [indexFields, oldIndexFields, setAlertParamsEntity, entity]);
const indexPopover = (
From 55a0dbbc097c1459d0e505291640cbc8c42134dd Mon Sep 17 00:00:00 2001
From: CJ Cenizal
Date: Mon, 14 Jun 2021 15:24:24 -0700
Subject: [PATCH 23/91] Discourage use of legacy index templates (#101533)
* Hide legacy index templates table if the user doesn't have any.
* Render deprecation warning above legacy index templates table and in legacy index template wizard.
* Update index template doc link to point to the new docs.
---
.../src/jest/utils/router_helpers.tsx | 37 ++++++--
.../kbn-test/src/jest/utils/testbed/types.ts | 5 +-
.../public/doc_links/doc_links_service.ts | 2 +-
.../home/index_templates_tab.test.ts | 22 ++++-
.../template_create.helpers.ts | 30 ++++---
.../template_create.test.tsx | 32 ++++++-
.../template_form.helpers.ts | 1 +
.../components/index_templates/index.ts | 10 ++-
.../legacy_index_template_deprecation.tsx | 84 +++++++++++++++++++
.../template_form/template_form.tsx | 30 +++++--
.../home/template_list/components/index.ts | 2 +-
.../home/template_list/template_list.tsx | 25 ++++--
.../template_clone/template_clone.tsx | 2 +
.../template_create/template_create.tsx | 4 +-
.../sections/template_edit/template_edit.tsx | 4 +-
15 files changed, 245 insertions(+), 45 deletions(-)
create mode 100644 x-pack/plugins/index_management/public/application/components/index_templates/legacy_index_template_deprecation.tsx
diff --git a/packages/kbn-test/src/jest/utils/router_helpers.tsx b/packages/kbn-test/src/jest/utils/router_helpers.tsx
index e2245440274d1..85ef27488a4ce 100644
--- a/packages/kbn-test/src/jest/utils/router_helpers.tsx
+++ b/packages/kbn-test/src/jest/utils/router_helpers.tsx
@@ -8,18 +8,39 @@
import React, { Component, ComponentType } from 'react';
import { MemoryRouter, Route, withRouter } from 'react-router-dom';
-import * as H from 'history';
+import { History, LocationDescriptor } from 'history';
-export const WithMemoryRouter = (initialEntries: string[] = ['/'], initialIndex: number = 0) => (
- WrappedComponent: ComponentType
-) => (props: any) => (
+const stringifyPath = (path: LocationDescriptor): string => {
+ if (typeof path === 'string') {
+ return path;
+ }
+
+ return path.pathname || '/';
+};
+
+const locationDescriptorToRoutePath = (
+ paths: LocationDescriptor | LocationDescriptor[]
+): string | string[] => {
+ if (Array.isArray(paths)) {
+ return paths.map((path: LocationDescriptor) => {
+ return stringifyPath(path);
+ });
+ }
+
+ return stringifyPath(paths);
+};
+
+export const WithMemoryRouter = (
+ initialEntries: LocationDescriptor[] = ['/'],
+ initialIndex: number = 0
+) => (WrappedComponent: ComponentType) => (props: any) => (
);
export const WithRoute = (
- componentRoutePath: string | string[] = '/',
+ componentRoutePath: LocationDescriptor | LocationDescriptor[] = ['/'],
onRouter = (router: any) => {}
) => (WrappedComponent: ComponentType) => {
// Create a class component that will catch the router
@@ -40,16 +61,16 @@ export const WithRoute = (
return (props: any) => (
}
/>
);
};
interface Router {
- history: Partial;
+ history: Partial;
route: {
- location: H.Location;
+ location: LocationDescriptor;
};
}
diff --git a/packages/kbn-test/src/jest/utils/testbed/types.ts b/packages/kbn-test/src/jest/utils/testbed/types.ts
index fdc000215c4f1..bba504951c0bc 100644
--- a/packages/kbn-test/src/jest/utils/testbed/types.ts
+++ b/packages/kbn-test/src/jest/utils/testbed/types.ts
@@ -8,6 +8,7 @@
import { Store } from 'redux';
import { ReactWrapper } from 'enzyme';
+import { LocationDescriptor } from 'history';
export type SetupFunc = (props?: any) => TestBed | Promise>;
@@ -161,11 +162,11 @@ export interface MemoryRouterConfig {
/** Flag to add or not the `MemoryRouter`. If set to `false`, there won't be any router and the component won't be wrapped on a ` `. */
wrapComponent?: boolean;
/** The React Router **initial entries** setting ([see documentation](https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/MemoryRouter.md)) */
- initialEntries?: string[];
+ initialEntries?: LocationDescriptor[];
/** The React Router **initial index** setting ([see documentation](https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/MemoryRouter.md)) */
initialIndex?: number;
/** The route **path** for the mounted component (defaults to `"/"`) */
- componentRoutePath?: string | string[];
+ componentRoutePath?: LocationDescriptor | LocationDescriptor[];
/** A callBack that will be called with the React Router instance once mounted */
onRouter?: (router: any) => void;
}
diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts
index 53428edf4b345..06277d9351922 100644
--- a/src/core/public/doc_links/doc_links_service.ts
+++ b/src/core/public/doc_links/doc_links_service.ts
@@ -142,7 +142,7 @@ export class DocLinksService {
dataStreams: `${ELASTICSEARCH_DOCS}data-streams.html`,
indexModules: `${ELASTICSEARCH_DOCS}index-modules.html`,
indexSettings: `${ELASTICSEARCH_DOCS}index-modules.html#index-modules-settings`,
- indexTemplates: `${ELASTICSEARCH_DOCS}indices-templates.html`,
+ indexTemplates: `${ELASTICSEARCH_DOCS}index-templates.html`,
mapping: `${ELASTICSEARCH_DOCS}mapping.html`,
mappingAnalyzer: `${ELASTICSEARCH_DOCS}analyzer.html`,
mappingCoerce: `${ELASTICSEARCH_DOCS}coerce.html`,
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
index e43f147a65800..bf1a78e3cfe90 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
@@ -31,7 +31,7 @@ describe('Index Templates tab', () => {
server.restore();
});
- describe('when there are no index templates', () => {
+ describe('when there are no index templates of either kind', () => {
test('should display an empty prompt', async () => {
httpRequestsMockHelpers.setLoadTemplatesResponse({ templates: [], legacyTemplates: [] });
@@ -46,6 +46,26 @@ describe('Index Templates tab', () => {
});
});
+ describe('when there are composable index templates but no legacy index templates', () => {
+ test('only the composable index templates table is visible', async () => {
+ httpRequestsMockHelpers.setLoadTemplatesResponse({
+ templates: [fixtures.getComposableTemplate()],
+ legacyTemplates: [],
+ });
+
+ await act(async () => {
+ testBed = await setup();
+ });
+ const { exists, component } = testBed;
+ component.update();
+
+ expect(exists('sectionLoading')).toBe(false);
+ expect(exists('emptyPrompt')).toBe(false);
+ expect(exists('templateTable')).toBe(true);
+ expect(exists('legacyTemplateTable')).toBe(false);
+ });
+ });
+
describe('when there are index templates', () => {
// Add a default loadIndexTemplate response
httpRequestsMockHelpers.setLoadTemplateResponse(fixtures.getTemplate());
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts
index 199ace6048bde..7d3b34a6b8238 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts
@@ -11,17 +11,23 @@ import { WithAppDependencies } from '../helpers';
import { formSetup, TestSubjects } from './template_form.helpers';
-const testBedConfig: TestBedConfig = {
- memoryRouter: {
- initialEntries: [`/create_template`],
- componentRoutePath: `/create_template`,
- },
- doMountAsync: true,
-};
+export const setup: any = (isLegacy: boolean = false) => {
+ const route = isLegacy
+ ? { pathname: '/create_template', search: '?legacy=true' }
+ : { pathname: '/create_template' };
+
+ const testBedConfig: TestBedConfig = {
+ memoryRouter: {
+ initialEntries: [route],
+ componentRoutePath: route,
+ },
+ doMountAsync: true,
+ };
-const initTestBed = registerTestBed(
- WithAppDependencies(TemplateCreate),
- testBedConfig
-);
+ const initTestBed = registerTestBed(
+ WithAppDependencies(TemplateCreate),
+ testBedConfig
+ );
-export const setup: any = formSetup.bind(null, initTestBed);
+ return formSetup.call(null, initTestBed);
+};
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx
index 9f435fac8b347..77ce172f3e0db 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx
@@ -101,7 +101,7 @@ describe(' ', () => {
(window as any)['__react-beautiful-dnd-disable-dev-warnings'] = false;
});
- describe('on component mount', () => {
+ describe('composable index template', () => {
beforeEach(async () => {
await act(async () => {
testBed = await setup();
@@ -115,6 +115,11 @@ describe(' ', () => {
expect(find('pageTitle').text()).toEqual('Create template');
});
+ test('renders no deprecation warning', async () => {
+ const { exists } = testBed;
+ expect(exists('legacyIndexTemplateDeprecationWarning')).toBe(false);
+ });
+
test('should not let the user go to the next step with invalid fields', async () => {
const { find, actions, component } = testBed;
@@ -129,6 +134,26 @@ describe(' ', () => {
});
});
+ describe('legacy index template', () => {
+ beforeEach(async () => {
+ await act(async () => {
+ testBed = await setup(true);
+ });
+ });
+
+ test('should set the correct page title', () => {
+ const { exists, find } = testBed;
+
+ expect(exists('pageTitle')).toBe(true);
+ expect(find('pageTitle').text()).toEqual('Create legacy template');
+ });
+
+ test('renders deprecation warning', async () => {
+ const { exists } = testBed;
+ expect(exists('legacyIndexTemplateDeprecationWarning')).toBe(true);
+ });
+ });
+
describe('form validation', () => {
beforeEach(async () => {
await act(async () => {
@@ -150,6 +175,11 @@ describe(' ', () => {
expect(find('stepTitle').text()).toEqual('Component templates (optional)');
});
+ it(`doesn't render the deprecated legacy index template warning`, () => {
+ const { exists } = testBed;
+ expect(exists('legacyIndexTemplateDeprecationWarning')).toBe(false);
+ });
+
it('should list the available component templates', () => {
const {
actions: {
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts
index 355bbc12f94fc..01aeba31770db 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts
@@ -306,6 +306,7 @@ export type TestSubjects =
| 'indexPatternsField'
| 'indexPatternsWarning'
| 'indexPatternsWarningDescription'
+ | 'legacyIndexTemplateDeprecationWarning'
| 'mappingsEditorFieldEdit'
| 'mockCodeEditor'
| 'mockComboBox'
diff --git a/x-pack/plugins/index_management/public/application/components/index_templates/index.ts b/x-pack/plugins/index_management/public/application/components/index_templates/index.ts
index a9131bab70551..d460175543ac5 100644
--- a/x-pack/plugins/index_management/public/application/components/index_templates/index.ts
+++ b/x-pack/plugins/index_management/public/application/components/index_templates/index.ts
@@ -5,4 +5,12 @@
* 2.0.
*/
-export * from './simulate_template';
+export {
+ SimulateTemplateFlyoutContent,
+ simulateTemplateFlyoutProps,
+ SimulateTemplateProps,
+ SimulateTemplate,
+ SimulateTemplateFilters,
+} from './simulate_template';
+
+export { LegacyIndexTemplatesDeprecation } from './legacy_index_template_deprecation';
diff --git a/x-pack/plugins/index_management/public/application/components/index_templates/legacy_index_template_deprecation.tsx b/x-pack/plugins/index_management/public/application/components/index_templates/legacy_index_template_deprecation.tsx
new file mode 100644
index 0000000000000..6fbea1760f3a4
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/index_templates/legacy_index_template_deprecation.tsx
@@ -0,0 +1,84 @@
+/*
+ * 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 { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+import { EuiCallOut, EuiLink } from '@elastic/eui';
+import { ScopedHistory } from 'kibana/public';
+import { reactRouterNavigate } from '../../../shared_imports';
+import { documentationService } from '../../services/documentation';
+
+interface Props {
+ history?: ScopedHistory;
+ showCta?: boolean;
+}
+
+export const LegacyIndexTemplatesDeprecation: React.FunctionComponent = ({
+ history,
+ showCta,
+}) => {
+ return (
+
+ {showCta && history && (
+
+
+
+
+ ),
+ learnMoreLink: (
+
+ {i18n.translate(
+ 'xpack.idxMgmt.home.legacyIndexTemplatesDeprecation.ctaLearnMoreLinkText',
+ {
+ defaultMessage: 'learn more.',
+ }
+ )}
+
+ ),
+ }}
+ />
+
+ )}
+
+ {!showCta && (
+
+ {i18n.translate('xpack.idxMgmt.home.legacyIndexTemplatesDeprecation.learnMoreLinkText', {
+ defaultMessage: 'Learn more.',
+ })}
+
+ )}
+
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
index ef9cde30907f0..54160141827d0 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
@@ -9,24 +9,26 @@ import React, { useState, useCallback, useRef } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiSpacer, EuiButton } from '@elastic/eui';
+import { ScopedHistory } from 'kibana/public';
import { TemplateDeserialized } from '../../../../common';
import { serializers, Forms, GlobalFlyout } from '../../../shared_imports';
+import {
+ CommonWizardSteps,
+ StepSettingsContainer,
+ StepMappingsContainer,
+ StepAliasesContainer,
+} from '../shared';
+import { documentationService } from '../../services/documentation';
import { SectionError } from '../section_error';
import {
SimulateTemplateFlyoutContent,
SimulateTemplateProps,
simulateTemplateFlyoutProps,
SimulateTemplateFilters,
+ LegacyIndexTemplatesDeprecation,
} from '../index_templates';
import { StepLogisticsContainer, StepComponentContainer, StepReviewContainer } from './steps';
-import {
- CommonWizardSteps,
- StepSettingsContainer,
- StepMappingsContainer,
- StepAliasesContainer,
-} from '../shared';
-import { documentationService } from '../../services/documentation';
const { stripEmptyFields } = serializers;
const { FormWizard, FormWizardStep } = Forms;
@@ -38,6 +40,7 @@ interface Props {
clearSaveError: () => void;
isSaving: boolean;
saveError: any;
+ history?: ScopedHistory;
isLegacy?: boolean;
defaultValue?: TemplateDeserialized;
isEditing?: boolean;
@@ -98,6 +101,7 @@ export const TemplateForm = ({
saveError,
clearSaveError,
onSave,
+ history,
}: Props) => {
const [wizardContent, setWizardContent] = useState | null>(null);
const { addContent: addContentToGlobalFlyout, closeFlyout } = useGlobalFlyout();
@@ -283,12 +287,20 @@ export const TemplateForm = ({
);
};
+ const isLegacyIndexTemplate = indexTemplate._kbnMeta.isLegacy === true;
+
return (
<>
{/* Form header */}
{title}
-
+
+
+ {isLegacyIndexTemplate && (
+
+ )}
+
+
defaultValue={wizardDefaultValue}
@@ -311,7 +323,7 @@ export const TemplateForm = ({
/>
- {indexTemplate._kbnMeta.isLegacy !== true && (
+ {!isLegacyIndexTemplate && (
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/components/index.ts b/x-pack/plugins/index_management/public/application/sections/home/template_list/components/index.ts
index b820bf559fb74..8b756be535ed2 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/components/index.ts
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/components/index.ts
@@ -5,4 +5,4 @@
* 2.0.
*/
-export * from './template_type_indicator';
+export { TemplateTypeIndicator } from './template_type_indicator';
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx
index ecd41455d3249..b8b5a8e3c7d1a 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx
@@ -24,7 +24,13 @@ import {
import { UIM_TEMPLATE_LIST_LOAD } from '../../../../../common/constants';
import { TemplateListItem } from '../../../../../common';
-import { SectionError, SectionLoading, Error } from '../../../components';
+import { attemptToURIDecode } from '../../../../shared_imports';
+import {
+ SectionError,
+ SectionLoading,
+ Error,
+ LegacyIndexTemplatesDeprecation,
+} from '../../../components';
import { useLoadIndexTemplates } from '../../../services/api';
import { documentationService } from '../../../services/documentation';
import { useServices } from '../../../app_context';
@@ -34,11 +40,10 @@ import {
getTemplateCloneLink,
} from '../../../services/routing';
import { getIsLegacyFromQueryParams } from '../../../lib/index_templates';
+import { FilterListButton, Filters } from '../components';
import { TemplateTable } from './template_table';
import { TemplateDetails } from './template_details';
import { LegacyTemplateTable } from './legacy_templates/template_table';
-import { FilterListButton, Filters } from '../components';
-import { attemptToURIDecode } from '../../../../shared_imports';
type FilterName = 'managed' | 'cloudManaged' | 'system';
interface MatchParams {
@@ -130,7 +135,7 @@ export const TemplateList: React.FunctionComponent
-
+
+
+
+
+
+
+
0 && renderLegacyTemplatesTable()}
);
}
diff --git a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx
index 37df44d175771..36bff298e345b 100644
--- a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx
@@ -9,6 +9,7 @@ import React, { useEffect, useState } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiPageBody, EuiPageContent, EuiTitle } from '@elastic/eui';
+import { ScopedHistory } from 'kibana/public';
import { TemplateDeserialized } from '../../../../common';
import { TemplateForm, SectionLoading, SectionError, Error } from '../../components';
@@ -114,6 +115,7 @@ export const TemplateClone: React.FunctionComponent
);
}
diff --git a/x-pack/plugins/index_management/public/application/sections/template_create/template_create.tsx b/x-pack/plugins/index_management/public/application/sections/template_create/template_create.tsx
index 32b6ce7181bfd..310807aeef38f 100644
--- a/x-pack/plugins/index_management/public/application/sections/template_create/template_create.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/template_create/template_create.tsx
@@ -11,10 +11,11 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { EuiPageBody, EuiPageContent, EuiTitle } from '@elastic/eui';
import { useLocation } from 'react-router-dom';
import { parse } from 'query-string';
+import { ScopedHistory } from 'kibana/public';
+import { TemplateDeserialized } from '../../../../common';
import { TemplateForm } from '../../components';
import { breadcrumbService } from '../../services/breadcrumbs';
-import { TemplateDeserialized } from '../../../../common';
import { saveTemplate } from '../../services/api';
import { getTemplateDetailsLink } from '../../services/routing';
@@ -76,6 +77,7 @@ export const TemplateCreate: React.FunctionComponent = ({ h
saveError={saveError}
clearSaveError={clearSaveError}
isLegacy={isLegacy}
+ history={history as ScopedHistory}
/>
diff --git a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx
index 716f85e5ff1c4..f4ffe97931a24 100644
--- a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx
@@ -9,14 +9,15 @@ import React, { useEffect, useState, Fragment } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiPageBody, EuiPageContent, EuiTitle, EuiSpacer, EuiCallOut } from '@elastic/eui';
+import { ScopedHistory } from 'kibana/public';
import { TemplateDeserialized } from '../../../../common';
+import { attemptToURIDecode } from '../../../shared_imports';
import { breadcrumbService } from '../../services/breadcrumbs';
import { useLoadIndexTemplate, updateTemplate } from '../../services/api';
import { getTemplateDetailsLink } from '../../services/routing';
import { SectionLoading, SectionError, TemplateForm, Error } from '../../components';
import { getIsLegacyFromQueryParams } from '../../lib/index_templates';
-import { attemptToURIDecode } from '../../../shared_imports';
interface MatchParams {
name: string;
@@ -154,6 +155,7 @@ export const TemplateEdit: React.FunctionComponent
);
From 61602fe2beed8e7afde3ded7356cd9b9100c8f82 Mon Sep 17 00:00:00 2001
From: ymao1
Date: Mon, 14 Jun 2021 20:40:43 -0400
Subject: [PATCH 24/91] [Alerting][Docs] Reformatting rule types docs (#101420)
* Reformatting rule types docs
* Apply suggestions from code review
Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com>
* Restructure geo rule page
* Apply suggestions from code review
Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com>
* PR fixes
Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com>
---
.../alerting/domain-specific-rules.asciidoc | 20 -------
docs/user/alerting/index.asciidoc | 3 +-
docs/user/alerting/rule-types.asciidoc | 56 +++++++++++++++++++
.../es-query.asciidoc | 0
.../geo-rule-types.asciidoc | 33 ++++-------
.../index-threshold.asciidoc | 0
docs/user/alerting/stack-rules.asciidoc | 27 ---------
7 files changed, 69 insertions(+), 70 deletions(-)
delete mode 100644 docs/user/alerting/domain-specific-rules.asciidoc
create mode 100644 docs/user/alerting/rule-types.asciidoc
rename docs/user/alerting/{stack-rules => rule-types}/es-query.asciidoc (100%)
rename docs/user/alerting/{map-rules => rule-types}/geo-rule-types.asciidoc (74%)
rename docs/user/alerting/{stack-rules => rule-types}/index-threshold.asciidoc (100%)
delete mode 100644 docs/user/alerting/stack-rules.asciidoc
diff --git a/docs/user/alerting/domain-specific-rules.asciidoc b/docs/user/alerting/domain-specific-rules.asciidoc
deleted file mode 100644
index f509f9e528823..0000000000000
--- a/docs/user/alerting/domain-specific-rules.asciidoc
+++ /dev/null
@@ -1,20 +0,0 @@
-[role="xpack"]
-[[domain-specific-rules]]
-== Domain-specific rules
-
-For domain-specific rules, refer to the documentation for that app.
-{kib} supports these rules:
-
-* {observability-guide}/create-alerts.html[Observability rules]
-* {security-guide}/prebuilt-rules.html[Security rules]
-* <>
-* {ml-docs}/ml-configuring-alerts.html[{ml-cap} rules] beta:[]
-
-[NOTE]
-==============================================
-Some rule types are subscription features, while others are free features.
-For a comparison of the Elastic subscription levels,
-see {subscriptions}[the subscription page].
-==============================================
-
-include::map-rules/geo-rule-types.asciidoc[]
diff --git a/docs/user/alerting/index.asciidoc b/docs/user/alerting/index.asciidoc
index 68cf3ee070b08..9ab6a2dc46ebf 100644
--- a/docs/user/alerting/index.asciidoc
+++ b/docs/user/alerting/index.asciidoc
@@ -3,6 +3,5 @@ include::alerting-setup.asciidoc[]
include::create-and-manage-rules.asciidoc[]
include::defining-rules.asciidoc[]
include::rule-management.asciidoc[]
-include::stack-rules.asciidoc[]
-include::domain-specific-rules.asciidoc[]
+include::rule-types.asciidoc[]
include::alerting-troubleshooting.asciidoc[]
diff --git a/docs/user/alerting/rule-types.asciidoc b/docs/user/alerting/rule-types.asciidoc
new file mode 100644
index 0000000000000..bb840014fe80f
--- /dev/null
+++ b/docs/user/alerting/rule-types.asciidoc
@@ -0,0 +1,56 @@
+[role="xpack"]
+[[rule-types]]
+== Rule types
+
+A rule is a set of <>, <>, and <> that enable notifications. {kib} provides two types of rules: rules specific to the Elastic Stack and rules specific to a domain.
+
+[NOTE]
+==============================================
+Some rule types are subscription features, while others are free features.
+For a comparison of the Elastic subscription levels,
+see {subscriptions}[the subscription page].
+==============================================
+
+[float]
+[[stack-rules]]
+=== Stack rules
+
+<> are built into {kib}. To access the *Stack Rules* feature and create and edit rules, users require the `all` privilege. See <> for more information.
+
+[cols="2*<"]
+|===
+
+| <>
+| Aggregate field values from documents using {es} queries, compare them to threshold values, and schedule actions to run when the thresholds are met.
+
+| <>
+| Run a user-configured {es} query, compare the number of matches to a configured threshold, and schedule actions to run when the threshold condition is met.
+
+|===
+
+[float]
+[[domain-specific-rules]]
+=== Domain rules
+
+Domain rules are registered by *Observability*, *Security*, <> and <>.
+
+[cols="2*<"]
+|===
+
+| {observability-guide}/create-alerts.html[Observability rules]
+| Detect complex conditions in the *Logs*, *Metrics*, and *Uptime* apps.
+
+| {security-guide}/prebuilt-rules.html[Security rules]
+| Detect suspicous source events with pre-built or custom rules and create alerts when a rule’s conditions are met.
+
+| <>
+| Run an {es} query to determine if any documents are currently contained in any boundaries from a specified boundary index and generate alerts when a rule's conditions are met.
+
+| {ml-docs}/ml-configuring-alerts.html[{ml-cap} rules] beta:[]
+| Run scheduled checks on an anomaly detection job to detect anomalies with certain conditions. If an anomaly meets the conditions, an alert is created and the associated action is triggered.
+
+|===
+
+include::rule-types/index-threshold.asciidoc[]
+include::rule-types/es-query.asciidoc[]
+include::rule-types/geo-rule-types.asciidoc[]
diff --git a/docs/user/alerting/stack-rules/es-query.asciidoc b/docs/user/alerting/rule-types/es-query.asciidoc
similarity index 100%
rename from docs/user/alerting/stack-rules/es-query.asciidoc
rename to docs/user/alerting/rule-types/es-query.asciidoc
diff --git a/docs/user/alerting/map-rules/geo-rule-types.asciidoc b/docs/user/alerting/rule-types/geo-rule-types.asciidoc
similarity index 74%
rename from docs/user/alerting/map-rules/geo-rule-types.asciidoc
rename to docs/user/alerting/rule-types/geo-rule-types.asciidoc
index eee7b59252205..244cf90c855a7 100644
--- a/docs/user/alerting/map-rules/geo-rule-types.asciidoc
+++ b/docs/user/alerting/rule-types/geo-rule-types.asciidoc
@@ -1,16 +1,14 @@
[role="xpack"]
[[geo-alerting]]
-=== Geo rule type
+=== Tracking containment
-Alerting now includes one additional stack rule: <>.
-
-As with other stack rules, you need `all` access to the *Stack Rules* feature
-to be able to create and edit a geo rule.
-See <> for more information on configuring roles that provide access to this feature.
+<> offers the Tracking containment rule type which runs an {es} query over indices to determine whether any
+documents are currently contained within any boundaries from the specified boundary index.
+In the event that an entity is contained within a boundary, an alert may be generated.
[float]
-==== Geo alerting requirements
-To create a *Tracking containment* rule, the following requirements must be present:
+==== Requirements
+To create a Tracking containment rule, the following requirements must be present:
- *Tracks index or index pattern*: An index containing a `geo_point` field, `date` field,
and some form of entity identifier. An entity identifier is a `keyword` or `number`
@@ -29,22 +27,12 @@ than the current time minus the amount of the interval. If data older than
`now - ` is ingested, it won't trigger a rule.
[float]
-==== Creating a geo rule
-Click the *Create* button in the <>.
-Complete the <>.
-
-[role="screenshot"]
-image::user/alerting/images/alert-types-tracking-select.png[Choosing a tracking rule type]
+==== Create the rule
-[float]
-[[rule-type-tracking-containment]]
-==== Tracking containment
-The Tracking containment rule type runs an {es} query over indices, determining if any
-documents are currently contained within any boundaries from the specified boundary index.
-In the event that an entity is contained within a boundary, an alert may be generated.
+Fill in the <>, then select Tracking containment.
[float]
-===== Defining the conditions
+==== Define the conditions
Tracking containment rules have 3 clauses that define the condition to detect,
as well as 2 Kuery bars used to provide additional filtering context for each of the indices.
@@ -61,6 +49,9 @@ Index (Boundary):: This clause requires an *index or index pattern*, a *`geo_sha
identifying boundaries, and an optional *Human-readable boundary name* for better alerting
messages.
+[float]
+==== Add action
+
Conditions for how a rule is tracked can be specified uniquely for each individual action.
A rule can be triggered either when a containment condition is met or when an entity
is no longer contained.
diff --git a/docs/user/alerting/stack-rules/index-threshold.asciidoc b/docs/user/alerting/rule-types/index-threshold.asciidoc
similarity index 100%
rename from docs/user/alerting/stack-rules/index-threshold.asciidoc
rename to docs/user/alerting/rule-types/index-threshold.asciidoc
diff --git a/docs/user/alerting/stack-rules.asciidoc b/docs/user/alerting/stack-rules.asciidoc
deleted file mode 100644
index 483834c78806e..0000000000000
--- a/docs/user/alerting/stack-rules.asciidoc
+++ /dev/null
@@ -1,27 +0,0 @@
-[role="xpack"]
-[[stack-rules]]
-== Stack rule types
-
-Kibana provides two types of rules:
-
-* Stack rules, which are built into {kib}
-* <>, which are registered by {kib} apps.
-
-{kib} provides two stack rules:
-
-* <>
-* <>
-
-Users require the `all` privilege to access the *Stack Rules* feature and create and edit rules.
-See <> for more information.
-
-[NOTE]
-==============================================
-Some rule types are subscription features, while others are free features.
-For a comparison of the Elastic subscription levels,
-see {subscriptions}[the subscription page].
-==============================================
-
-
-include::stack-rules/index-threshold.asciidoc[]
-include::stack-rules/es-query.asciidoc[]
From 6351d51f6d222e9847722feebdfd946a57ed12da Mon Sep 17 00:00:00 2001
From: Stratoula Kalafateli
Date: Tue, 15 Jun 2021 09:29:59 +0300
Subject: [PATCH 25/91] [Tagcloud] Replaces current implementation with
elastic-charts (#100017)
* WIP - Replace tagcloud with es-charts wordcloud
* Cleanup and add unit tests
* Fix interpreter test
* Update all tagcloud snapshots
* Partial fix tagcloud test
* Fix some other functional tests, add migration script, update sample data
* Replace getColor with getCategorixalColor
* Fix functional test
* Apply clickhandler event for filtering by clicking the word
* Fix weight calculation
* Add a unit test and fix functional
* Change the cursor to pointer
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
package.json | 1 -
.../data_sets/ecommerce/saved_objects.ts | 2 +-
.../data_sets/flights/saved_objects.ts | 2 +-
.../__snapshots__/tag_cloud_fn.test.ts.snap | 11 +
.../public/__snapshots__/to_ast.test.ts.snap | 3 +
.../__snapshots__/tag_cloud.test.js.snap | 3 -
.../tag_cloud_visualization.test.js.snap | 7 -
.../public/components/feedback_message.js | 51 --
.../components/get_tag_cloud_options.tsx | 17 +
.../public/components/label.js | 27 -
.../public/components/tag_cloud.js | 409 --------------
.../public/components/tag_cloud.scss | 16 +-
.../public/components/tag_cloud.test.js | 507 ------------------
.../components/tag_cloud_chart.test.tsx | 150 ++++++
.../public/components/tag_cloud_chart.tsx | 235 ++++++--
.../public/components/tag_cloud_options.tsx | 39 +-
.../components/tag_cloud_visualization.js | 155 ------
.../tag_cloud_visualization.test.js | 128 -----
.../vis_type_tagcloud/public/plugin.ts | 12 +-
.../public/tag_cloud_fn.test.ts | 1 +
.../vis_type_tagcloud/public/tag_cloud_fn.ts | 31 +-
.../public/tag_cloud_type.ts | 15 +-
.../public/tag_cloud_vis_renderer.tsx | 23 +-
.../vis_type_tagcloud/public/to_ast.test.ts | 5 +
.../vis_type_tagcloud/public/to_ast.ts | 3 +-
src/plugins/vis_type_tagcloud/public/types.ts | 28 +-
.../visualize_embeddable_factory.ts | 11 +-
.../visualization_common_migrations.ts | 22 +
...ualization_saved_object_migrations.test.ts | 41 ++
.../visualization_saved_object_migrations.ts | 26 +-
test/functional/apps/visualize/_tag_cloud.ts | 10 +-
.../functional/page_objects/tag_cloud_page.ts | 13 +-
.../services/dashboard/expectations.ts | 5 +-
.../snapshots/baseline/partial_test_1.json | 2 +-
.../snapshots/baseline/tagcloud_all_data.json | 2 +-
.../snapshots/baseline/tagcloud_fontsize.json | 2 +-
.../baseline/tagcloud_invalid_data.json | 2 +-
.../baseline/tagcloud_metric_data.json | 2 +-
.../snapshots/baseline/tagcloud_options.json | 2 +-
.../snapshots/session/partial_test_1.json | 2 +-
.../snapshots/session/tagcloud_all_data.json | 2 +-
.../snapshots/session/tagcloud_fontsize.json | 2 +-
.../session/tagcloud_invalid_data.json | 2 +-
.../session/tagcloud_metric_data.json | 2 +-
.../snapshots/session/tagcloud_options.json | 2 +-
yarn.lock | 2 +-
46 files changed, 642 insertions(+), 1393 deletions(-)
delete mode 100644 src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud.test.js.snap
delete mode 100644 src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap
delete mode 100644 src/plugins/vis_type_tagcloud/public/components/feedback_message.js
create mode 100644 src/plugins/vis_type_tagcloud/public/components/get_tag_cloud_options.tsx
delete mode 100644 src/plugins/vis_type_tagcloud/public/components/label.js
delete mode 100644 src/plugins/vis_type_tagcloud/public/components/tag_cloud.js
delete mode 100644 src/plugins/vis_type_tagcloud/public/components/tag_cloud.test.js
create mode 100644 src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.test.tsx
delete mode 100644 src/plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.js
delete mode 100644 src/plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.test.js
diff --git a/package.json b/package.json
index 513352db3f81b..ff2f62f513084 100644
--- a/package.json
+++ b/package.json
@@ -215,7 +215,6 @@
"cytoscape-dagre": "^2.2.2",
"d3": "3.5.17",
"d3-array": "1.2.4",
- "d3-cloud": "1.2.5",
"d3-scale": "1.0.7",
"d3-shape": "^1.1.0",
"d3-time": "^1.1.0",
diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts
index a12a2ff195211..267769d33fba2 100644
--- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts
+++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts
@@ -280,7 +280,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[eCommerce] Top Selling Products',
}),
visState:
- '{"title":"[eCommerce] Top Selling Products","type":"tagcloud","params":{"scale":"linear","orientation":"single","minFontSize":18,"maxFontSize":72,"showLabel":false},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"products.product_name.keyword","size":7,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}',
+ '{"title":"[eCommerce] Top Selling Products","type":"tagcloud","params":{"scale":"linear","orientation":"single","minFontSize":18,"maxFontSize":72,"showLabel":false,"palette":{"type":"palette","name":"default"}},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"products.product_name.keyword","size":7,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}',
uiStateJSON: '{}',
description: '',
version: 1,
diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts
index 05a3d012d707c..816322dbe5299 100644
--- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts
+++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts
@@ -242,7 +242,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[Flights] Destination Weather',
}),
visState:
- '{"title":"[Flights] Destination Weather","type":"tagcloud","params":{"scale":"linear","orientation":"single","minFontSize":18,"maxFontSize":72,"showLabel":false},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"DestWeather","size":10,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}',
+ '{"title":"[Flights] Destination Weather","type":"tagcloud","params":{"scale":"linear","orientation":"single","minFontSize":18,"maxFontSize":72,"showLabel":false,"palette":{"type":"palette","name":"default"}},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"DestWeather","size":10,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}',
uiStateJSON: '{}',
description: '',
version: 1,
diff --git a/src/plugins/vis_type_tagcloud/public/__snapshots__/tag_cloud_fn.test.ts.snap b/src/plugins/vis_type_tagcloud/public/__snapshots__/tag_cloud_fn.test.ts.snap
index 17a91a4d43cc7..cbfece0b081c6 100644
--- a/src/plugins/vis_type_tagcloud/public/__snapshots__/tag_cloud_fn.test.ts.snap
+++ b/src/plugins/vis_type_tagcloud/public/__snapshots__/tag_cloud_fn.test.ts.snap
@@ -5,6 +5,7 @@ Object {
"as": "tagloud_vis",
"type": "render",
"value": Object {
+ "syncColors": false,
"visData": Object {
"columns": Array [
Object {
@@ -20,6 +21,12 @@ Object {
"type": "datatable",
},
"visParams": Object {
+ "bucket": Object {
+ "accessor": 1,
+ "format": Object {
+ "id": "number",
+ },
+ },
"maxFontSize": 72,
"metric": Object {
"accessor": 0,
@@ -29,6 +36,10 @@ Object {
},
"minFontSize": 18,
"orientation": "single",
+ "palette": Object {
+ "name": "default",
+ "type": "palette",
+ },
"scale": "linear",
"showLabel": true,
},
diff --git a/src/plugins/vis_type_tagcloud/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/vis_type_tagcloud/public/__snapshots__/to_ast.test.ts.snap
index a8bc0b4c51678..fed6fb54288f2 100644
--- a/src/plugins/vis_type_tagcloud/public/__snapshots__/to_ast.test.ts.snap
+++ b/src/plugins/vis_type_tagcloud/public/__snapshots__/to_ast.test.ts.snap
@@ -84,6 +84,9 @@ Object {
"orientation": Array [
"single",
],
+ "palette": Array [
+ "default",
+ ],
"scale": Array [
"linear",
],
diff --git a/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud.test.js.snap b/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud.test.js.snap
deleted file mode 100644
index 88ed7c66a79a2..0000000000000
--- a/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud.test.js.snap
+++ /dev/null
@@ -1,3 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`tag cloud tests tagcloudscreenshot should render simple image 1`] = `"foo bar foobar "`;
diff --git a/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap b/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap
deleted file mode 100644
index d7707f64d8a4f..0000000000000
--- a/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap
+++ /dev/null
@@ -1,7 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`TagCloudVisualizationTest TagCloudVisualization - basics simple draw 1`] = `"CN IN US DE BR "`;
-
-exports[`TagCloudVisualizationTest TagCloudVisualization - basics with param change 1`] = `"CN IN US DE BR "`;
-
-exports[`TagCloudVisualizationTest TagCloudVisualization - basics with resize 1`] = `"CN IN US DE BR "`;
diff --git a/src/plugins/vis_type_tagcloud/public/components/feedback_message.js b/src/plugins/vis_type_tagcloud/public/components/feedback_message.js
deleted file mode 100644
index 9e1d66b0a2faa..0000000000000
--- a/src/plugins/vis_type_tagcloud/public/components/feedback_message.js
+++ /dev/null
@@ -1,51 +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 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import React, { Component, Fragment } from 'react';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiIconTip } from '@elastic/eui';
-
-export class FeedbackMessage extends Component {
- constructor() {
- super();
- this.state = { shouldShowTruncate: false, shouldShowIncomplete: false };
- }
-
- render() {
- if (!this.state.shouldShowTruncate && !this.state.shouldShowIncomplete) {
- return '';
- }
-
- return (
-
- {this.state.shouldShowTruncate && (
-
-
-
- )}
- {this.state.shouldShowIncomplete && (
-
-
-
- )}
-
- }
- />
- );
- }
-}
diff --git a/src/plugins/vis_type_tagcloud/public/components/get_tag_cloud_options.tsx b/src/plugins/vis_type_tagcloud/public/components/get_tag_cloud_options.tsx
new file mode 100644
index 0000000000000..82663bbf7070c
--- /dev/null
+++ b/src/plugins/vis_type_tagcloud/public/components/get_tag_cloud_options.tsx
@@ -0,0 +1,17 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { lazy } from 'react';
+import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
+import { TagCloudVisParams, TagCloudTypeProps } from '../types';
+
+const TagCloudOptionsLazy = lazy(() => import('./tag_cloud_options'));
+
+export const getTagCloudOptions = ({ palettes }: TagCloudTypeProps) => (
+ props: VisEditorOptionsProps
+) => ;
diff --git a/src/plugins/vis_type_tagcloud/public/components/label.js b/src/plugins/vis_type_tagcloud/public/components/label.js
deleted file mode 100644
index 028a001cfbe63..0000000000000
--- a/src/plugins/vis_type_tagcloud/public/components/label.js
+++ /dev/null
@@ -1,27 +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 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import React, { Component } from 'react';
-
-export class Label extends Component {
- constructor() {
- super();
- this.state = { label: '', shouldShowLabel: true };
- }
-
- render() {
- return (
-
- {this.state.label}
-
- );
- }
-}
diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud.js b/src/plugins/vis_type_tagcloud/public/components/tag_cloud.js
deleted file mode 100644
index 254d210eebf37..0000000000000
--- a/src/plugins/vis_type_tagcloud/public/components/tag_cloud.js
+++ /dev/null
@@ -1,409 +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 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import d3 from 'd3';
-import d3TagCloud from 'd3-cloud';
-import { EventEmitter } from 'events';
-
-const ORIENTATIONS = {
- single: () => 0,
- 'right angled': (tag) => {
- return hashWithinRange(tag.text, 2) * 90;
- },
- multiple: (tag) => {
- return hashWithinRange(tag.text, 12) * 15 - 90; //fan out 12 * 15 degrees over top-right and bottom-right quadrant (=-90 deg offset)
- },
-};
-const D3_SCALING_FUNCTIONS = {
- linear: () => d3.scale.linear(),
- log: () => d3.scale.log(),
- 'square root': () => d3.scale.sqrt(),
-};
-
-export class TagCloud extends EventEmitter {
- constructor(domNode, colorScale) {
- super();
-
- //DOM
- this._element = domNode;
- this._d3SvgContainer = d3.select(this._element).append('svg');
- this._svgGroup = this._d3SvgContainer.append('g');
- this._size = [1, 1];
- this.resize();
-
- //SETTING (non-configurable)
- /**
- * the fontFamily should be set explicitly for calculating a layout
- * and to avoid words overlapping
- */
- this._fontFamily = 'Inter UI, sans-serif';
- this._fontStyle = 'normal';
- this._fontWeight = 'normal';
- this._spiral = 'archimedean'; //layout shape
- this._timeInterval = 1000; //time allowed for layout algorithm
- this._padding = 5;
-
- //OPTIONS
- this._orientation = 'single';
- this._minFontSize = 10;
- this._maxFontSize = 36;
- this._textScale = 'linear';
- this._optionsAsString = null;
-
- //DATA
- this._words = null;
-
- //UTIL
- this._colorScale = colorScale;
- this._setTimeoutId = null;
- this._pendingJob = null;
- this._layoutIsUpdating = null;
- this._allInViewBox = false;
- this._DOMisUpdating = false;
- }
-
- setOptions(options) {
- if (JSON.stringify(options) === this._optionsAsString) {
- return;
- }
- this._optionsAsString = JSON.stringify(options);
- this._orientation = options.orientation;
- this._minFontSize = Math.min(options.minFontSize, options.maxFontSize);
- this._maxFontSize = Math.max(options.minFontSize, options.maxFontSize);
- this._textScale = options.scale;
- this._invalidate(false);
- }
-
- resize() {
- const newWidth = this._element.offsetWidth;
- const newHeight = this._element.offsetHeight;
-
- if (newWidth === this._size[0] && newHeight === this._size[1]) {
- return;
- }
-
- const wasInside = this._size[0] >= this._cloudWidth && this._size[1] >= this._cloudHeight;
- const willBeInside = this._cloudWidth <= newWidth && this._cloudHeight <= newHeight;
- this._size[0] = newWidth;
- this._size[1] = newHeight;
- if (wasInside && willBeInside && this._allInViewBox) {
- this._invalidate(true);
- } else {
- this._invalidate(false);
- }
- }
-
- setData(data) {
- this._words = data;
- this._invalidate(false);
- }
-
- destroy() {
- clearTimeout(this._setTimeoutId);
- this._element.innerHTML = '';
- }
-
- getStatus() {
- return this._allInViewBox ? TagCloud.STATUS.COMPLETE : TagCloud.STATUS.INCOMPLETE;
- }
-
- _updateContainerSize() {
- this._d3SvgContainer.attr('width', this._size[0]);
- this._d3SvgContainer.attr('height', this._size[1]);
- this._svgGroup.attr('width', this._size[0]);
- this._svgGroup.attr('height', this._size[1]);
- }
-
- _isJobRunning() {
- return this._setTimeoutId || this._layoutIsUpdating || this._DOMisUpdating;
- }
-
- async _processPendingJob() {
- if (!this._pendingJob) {
- return;
- }
-
- if (this._isJobRunning()) {
- return;
- }
-
- this._completedJob = null;
- const job = await this._pickPendingJob();
- if (job.words.length) {
- if (job.refreshLayout) {
- await this._updateLayout(job);
- }
- await this._updateDOM(job);
- const cloudBBox = this._svgGroup[0][0].getBBox();
- this._cloudWidth = cloudBBox.width;
- this._cloudHeight = cloudBBox.height;
- this._allInViewBox =
- cloudBBox.x >= 0 &&
- cloudBBox.y >= 0 &&
- cloudBBox.x + cloudBBox.width <= this._element.offsetWidth &&
- cloudBBox.y + cloudBBox.height <= this._element.offsetHeight;
- } else {
- this._emptyDOM(job);
- }
-
- if (this._pendingJob) {
- this._processPendingJob(); //pick up next job
- } else {
- this._completedJob = job;
- this.emit('renderComplete');
- }
- }
-
- async _pickPendingJob() {
- return await new Promise((resolve) => {
- this._setTimeoutId = setTimeout(async () => {
- const job = this._pendingJob;
- this._pendingJob = null;
- this._setTimeoutId = null;
- resolve(job);
- }, 0);
- });
- }
-
- _emptyDOM() {
- this._svgGroup.selectAll('text').remove();
- this._cloudWidth = 0;
- this._cloudHeight = 0;
- this._allInViewBox = true;
- this._DOMisUpdating = false;
- }
-
- async _updateDOM(job) {
- const canSkipDomUpdate = this._pendingJob || this._setTimeoutId;
- if (canSkipDomUpdate) {
- this._DOMisUpdating = false;
- return;
- }
-
- this._DOMisUpdating = true;
- const affineTransform = positionWord.bind(
- null,
- this._element.offsetWidth / 2,
- this._element.offsetHeight / 2
- );
- const svgTextNodes = this._svgGroup.selectAll('text');
- const stage = svgTextNodes.data(job.words, getText);
-
- await new Promise((resolve) => {
- const enterSelection = stage.enter();
- const enteringTags = enterSelection.append('text');
- enteringTags.style('font-size', getSizeInPixels);
- enteringTags.style('font-style', this._fontStyle);
- enteringTags.style('font-weight', () => this._fontWeight);
- enteringTags.style('font-family', () => this._fontFamily);
- enteringTags.style('fill', this.getFill.bind(this));
- enteringTags.attr('text-anchor', () => 'middle');
- enteringTags.attr('transform', affineTransform);
- enteringTags.attr('data-test-subj', getDisplayText);
- enteringTags.text(getDisplayText);
-
- const self = this;
- enteringTags.on({
- click: function (event) {
- self.emit('select', event);
- },
- mouseover: function () {
- d3.select(this).style('cursor', 'pointer');
- },
- mouseout: function () {
- d3.select(this).style('cursor', 'default');
- },
- });
-
- const movingTags = stage.transition();
- movingTags.duration(600);
- movingTags.style('font-size', getSizeInPixels);
- movingTags.style('font-style', this._fontStyle);
- movingTags.style('font-weight', () => this._fontWeight);
- movingTags.style('font-family', () => this._fontFamily);
- movingTags.attr('transform', affineTransform);
-
- const exitingTags = stage.exit();
- const exitTransition = exitingTags.transition();
- exitTransition.duration(200);
- exitingTags.style('fill-opacity', 1e-6);
- exitingTags.attr('font-size', 1);
- exitingTags.remove();
-
- let exits = 0;
- let moves = 0;
- const resolveWhenDone = () => {
- if (exits === 0 && moves === 0) {
- this._DOMisUpdating = false;
- resolve(true);
- }
- };
- exitTransition.each(() => exits++);
- exitTransition.each('end', () => {
- exits--;
- resolveWhenDone();
- });
- movingTags.each(() => moves++);
- movingTags.each('end', () => {
- moves--;
- resolveWhenDone();
- });
- });
- }
-
- _makeTextSizeMapper() {
- const mapSizeToFontSize = D3_SCALING_FUNCTIONS[this._textScale]();
- const range =
- this._words.length === 1
- ? [this._maxFontSize, this._maxFontSize]
- : [this._minFontSize, this._maxFontSize];
- mapSizeToFontSize.range(range);
- if (this._words) {
- mapSizeToFontSize.domain(d3.extent(this._words, getValue));
- }
- return mapSizeToFontSize;
- }
-
- _makeNewJob() {
- return {
- refreshLayout: true,
- size: this._size.slice(),
- words: this._words,
- };
- }
-
- _makeJobPreservingLayout() {
- return {
- refreshLayout: false,
- size: this._size.slice(),
- words: this._completedJob.words.map((tag) => {
- return {
- x: tag.x,
- y: tag.y,
- rotate: tag.rotate,
- size: tag.size,
- rawText: tag.rawText || tag.text,
- displayText: tag.displayText,
- meta: tag.meta,
- };
- }),
- };
- }
-
- _invalidate(keepLayout) {
- if (!this._words) {
- return;
- }
-
- this._updateContainerSize();
-
- const canReuseLayout = keepLayout && !this._isJobRunning() && this._completedJob;
- this._pendingJob = canReuseLayout ? this._makeJobPreservingLayout() : this._makeNewJob();
- this._processPendingJob();
- }
-
- async _updateLayout(job) {
- if (job.size[0] <= 0 || job.size[1] <= 0) {
- // If either width or height isn't above 0 we don't relayout anything,
- // since the d3-cloud will be stuck in an infinite loop otherwise.
- return;
- }
-
- const mapSizeToFontSize = this._makeTextSizeMapper();
- const tagCloudLayoutGenerator = d3TagCloud();
- tagCloudLayoutGenerator.size(job.size);
- tagCloudLayoutGenerator.padding(this._padding);
- tagCloudLayoutGenerator.rotate(ORIENTATIONS[this._orientation]);
- tagCloudLayoutGenerator.font(this._fontFamily);
- tagCloudLayoutGenerator.fontStyle(this._fontStyle);
- tagCloudLayoutGenerator.fontWeight(this._fontWeight);
- tagCloudLayoutGenerator.fontSize((tag) => mapSizeToFontSize(tag.value));
- tagCloudLayoutGenerator.random(seed);
- tagCloudLayoutGenerator.spiral(this._spiral);
- tagCloudLayoutGenerator.words(job.words);
- tagCloudLayoutGenerator.text(getDisplayText);
- tagCloudLayoutGenerator.timeInterval(this._timeInterval);
-
- this._layoutIsUpdating = true;
- await new Promise((resolve) => {
- tagCloudLayoutGenerator.on('end', () => {
- this._layoutIsUpdating = false;
- resolve(true);
- });
- tagCloudLayoutGenerator.start();
- });
- }
-
- /**
- * Returns debug info. For debugging only.
- * @return {*}
- */
- getDebugInfo() {
- const debug = {};
- debug.positions = this._completedJob
- ? this._completedJob.words.map((tag) => {
- return {
- displayText: tag.displayText,
- rawText: tag.rawText || tag.text,
- x: tag.x,
- y: tag.y,
- rotate: tag.rotate,
- };
- })
- : [];
- debug.size = {
- width: this._size[0],
- height: this._size[1],
- };
- return debug;
- }
-
- getFill(tag) {
- return this._colorScale(tag.text);
- }
-}
-
-TagCloud.STATUS = { COMPLETE: 0, INCOMPLETE: 1 };
-
-function seed() {
- return 0.5; //constant seed (not random) to ensure constant layouts for identical data
-}
-
-function getText(word) {
- return word.rawText;
-}
-
-function getDisplayText(word) {
- return word.displayText;
-}
-
-function positionWord(xTranslate, yTranslate, word) {
- if (isNaN(word.x) || isNaN(word.y) || isNaN(word.rotate)) {
- //move off-screen
- return `translate(${xTranslate * 3}, ${yTranslate * 3})rotate(0)`;
- }
-
- return `translate(${word.x + xTranslate}, ${word.y + yTranslate})rotate(${word.rotate})`;
-}
-
-function getValue(tag) {
- return tag.value;
-}
-
-function getSizeInPixels(tag) {
- return `${tag.size}px`;
-}
-
-function hashWithinRange(str, max) {
- str = JSON.stringify(str);
- let hash = 0;
- for (const ch of str) {
- hash = (hash * 31 + ch.charCodeAt(0)) % max;
- }
- return Math.abs(hash) % max;
-}
diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud.scss b/src/plugins/vis_type_tagcloud/public/components/tag_cloud.scss
index 37867f1ed1c17..51b5e9dedd844 100644
--- a/src/plugins/vis_type_tagcloud/public/components/tag_cloud.scss
+++ b/src/plugins/vis_type_tagcloud/public/components/tag_cloud.scss
@@ -5,18 +5,14 @@
// tgcChart__legend--small
// tgcChart__legend-isLoading
-.tgcChart__container, .tgcChart__wrapper {
+.tgcChart__wrapper {
flex: 1 1 0;
display: flex;
+ flex-direction: column;
}
-.tgcChart {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- overflow: hidden;
+.tgcChart__wrapper text {
+ cursor: pointer;
}
.tgcChart__label {
@@ -24,3 +20,7 @@
text-align: center;
font-weight: $euiFontWeightBold;
}
+
+.tgcChart__warning {
+ width: $euiSize;
+}
diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud.test.js b/src/plugins/vis_type_tagcloud/public/components/tag_cloud.test.js
deleted file mode 100644
index eb575457146c5..0000000000000
--- a/src/plugins/vis_type_tagcloud/public/components/tag_cloud.test.js
+++ /dev/null
@@ -1,507 +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 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import _ from 'lodash';
-import d3 from 'd3';
-import 'jest-canvas-mock';
-
-import { fromNode, delay } from 'bluebird';
-import { TagCloud } from './tag_cloud';
-import { setHTMLElementOffset, setSVGElementGetBBox } from '@kbn/test/jest';
-
-describe('tag cloud tests', () => {
- let SVGElementGetBBoxSpyInstance;
- let HTMLElementOffsetMockInstance;
-
- beforeEach(() => {
- setupDOM();
- });
-
- afterEach(() => {
- SVGElementGetBBoxSpyInstance.mockRestore();
- HTMLElementOffsetMockInstance.mockRestore();
- });
-
- const minValue = 1;
- const maxValue = 9;
- const midValue = (minValue + maxValue) / 2;
- const baseTest = {
- data: [
- { rawText: 'foo', displayText: 'foo', value: minValue },
- { rawText: 'bar', displayText: 'bar', value: midValue },
- { rawText: 'foobar', displayText: 'foobar', value: maxValue },
- ],
- options: {
- orientation: 'single',
- scale: 'linear',
- minFontSize: 10,
- maxFontSize: 36,
- },
- expected: [
- {
- text: 'foo',
- fontSize: '10px',
- },
- {
- text: 'bar',
- fontSize: '23px',
- },
- {
- text: 'foobar',
- fontSize: '36px',
- },
- ],
- };
-
- const singleLayoutTest = _.cloneDeep(baseTest);
-
- const rightAngleLayoutTest = _.cloneDeep(baseTest);
- rightAngleLayoutTest.options.orientation = 'right angled';
-
- const multiLayoutTest = _.cloneDeep(baseTest);
- multiLayoutTest.options.orientation = 'multiple';
-
- const mapWithLog = d3.scale.log();
- mapWithLog.range([baseTest.options.minFontSize, baseTest.options.maxFontSize]);
- mapWithLog.domain([minValue, maxValue]);
- const logScaleTest = _.cloneDeep(baseTest);
- logScaleTest.options.scale = 'log';
- logScaleTest.expected[1].fontSize = Math.round(mapWithLog(midValue)) + 'px';
-
- const mapWithSqrt = d3.scale.sqrt();
- mapWithSqrt.range([baseTest.options.minFontSize, baseTest.options.maxFontSize]);
- mapWithSqrt.domain([minValue, maxValue]);
- const sqrtScaleTest = _.cloneDeep(baseTest);
- sqrtScaleTest.options.scale = 'square root';
- sqrtScaleTest.expected[1].fontSize = Math.round(mapWithSqrt(midValue)) + 'px';
-
- const biggerFontTest = _.cloneDeep(baseTest);
- biggerFontTest.options.minFontSize = 36;
- biggerFontTest.options.maxFontSize = 72;
- biggerFontTest.expected[0].fontSize = '36px';
- biggerFontTest.expected[1].fontSize = '54px';
- biggerFontTest.expected[2].fontSize = '72px';
-
- const trimDataTest = _.cloneDeep(baseTest);
- trimDataTest.data.splice(1, 1);
- trimDataTest.expected.splice(1, 1);
-
- let domNode;
- let tagCloud;
-
- const colorScale = d3.scale
- .ordinal()
- .range(['#00a69b', '#57c17b', '#6f87d8', '#663db8', '#bc52bc', '#9e3533', '#daa05d']);
-
- function setupDOM() {
- domNode = document.createElement('div');
- SVGElementGetBBoxSpyInstance = setSVGElementGetBBox();
- HTMLElementOffsetMockInstance = setHTMLElementOffset(512, 512);
-
- document.body.appendChild(domNode);
- }
-
- function teardownDOM() {
- domNode.innerHTML = '';
- document.body.removeChild(domNode);
- }
-
- [
- singleLayoutTest,
- rightAngleLayoutTest,
- multiLayoutTest,
- logScaleTest,
- sqrtScaleTest,
- biggerFontTest,
- trimDataTest,
- ].forEach(function (currentTest) {
- describe(`should position elements correctly for options: ${JSON.stringify(
- currentTest.options
- )}`, () => {
- beforeEach(async () => {
- tagCloud = new TagCloud(domNode, colorScale);
- tagCloud.setData(currentTest.data);
- tagCloud.setOptions(currentTest.options);
- await fromNode((cb) => tagCloud.once('renderComplete', cb));
- });
-
- afterEach(teardownDOM);
-
- test(
- 'completeness should be ok',
- handleExpectedBlip(() => {
- expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.COMPLETE);
- })
- );
-
- test(
- 'positions should be ok',
- handleExpectedBlip(() => {
- const textElements = domNode.querySelectorAll('text');
- verifyTagProperties(currentTest.expected, textElements, tagCloud);
- })
- );
- });
- });
-
- [5, 100, 200, 300, 500].forEach((timeout) => {
- // FLAKY: https://github.com/elastic/kibana/issues/94043
- describe.skip(`should only send single renderComplete event at the very end, using ${timeout}ms timeout`, () => {
- beforeEach(async () => {
- //TagCloud takes at least 600ms to complete (due to d3 animation)
- //renderComplete should only notify at the last one
- tagCloud = new TagCloud(domNode, colorScale);
- tagCloud.setData(baseTest.data);
- tagCloud.setOptions(baseTest.options);
-
- //this timeout modifies the settings before the cloud is rendered.
- //the cloud needs to use the correct options
- setTimeout(() => tagCloud.setOptions(logScaleTest.options), timeout);
- await fromNode((cb) => tagCloud.once('renderComplete', cb));
- });
-
- afterEach(teardownDOM);
-
- test(
- 'completeness should be ok',
- handleExpectedBlip(() => {
- expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.COMPLETE);
- })
- );
-
- test(
- 'positions should be ok',
- handleExpectedBlip(() => {
- const textElements = domNode.querySelectorAll('text');
- verifyTagProperties(logScaleTest.expected, textElements, tagCloud);
- })
- );
- });
- });
-
- describe('should use the latest state before notifying (when modifying options multiple times)', () => {
- beforeEach(async () => {
- tagCloud = new TagCloud(domNode, colorScale);
- tagCloud.setData(baseTest.data);
- tagCloud.setOptions(baseTest.options);
- tagCloud.setOptions(logScaleTest.options);
- await fromNode((cb) => tagCloud.once('renderComplete', cb));
- });
-
- afterEach(teardownDOM);
-
- test(
- 'completeness should be ok',
- handleExpectedBlip(() => {
- expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.COMPLETE);
- })
- );
- test(
- 'positions should be ok',
- handleExpectedBlip(() => {
- const textElements = domNode.querySelectorAll('text');
- verifyTagProperties(logScaleTest.expected, textElements, tagCloud);
- })
- );
- });
-
- describe('should use the latest state before notifying (when modifying data multiple times)', () => {
- beforeEach(async () => {
- tagCloud = new TagCloud(domNode, colorScale);
- tagCloud.setData(baseTest.data);
- tagCloud.setOptions(baseTest.options);
- tagCloud.setData(trimDataTest.data);
-
- await fromNode((cb) => tagCloud.once('renderComplete', cb));
- });
-
- afterEach(teardownDOM);
-
- test(
- 'completeness should be ok',
- handleExpectedBlip(() => {
- expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.COMPLETE);
- })
- );
- test(
- 'positions should be ok',
- handleExpectedBlip(() => {
- const textElements = domNode.querySelectorAll('text');
- verifyTagProperties(trimDataTest.expected, textElements, tagCloud);
- })
- );
- });
-
- describe('should not get multiple render-events', () => {
- let counter;
- beforeEach(() => {
- counter = 0;
-
- return new Promise((resolve, reject) => {
- tagCloud = new TagCloud(domNode, colorScale);
- tagCloud.setData(baseTest.data);
- tagCloud.setOptions(baseTest.options);
-
- setTimeout(() => {
- //this should be overridden by later changes
- tagCloud.setData(sqrtScaleTest.data);
- tagCloud.setOptions(sqrtScaleTest.options);
- }, 100);
-
- setTimeout(() => {
- //latest change
- tagCloud.setData(logScaleTest.data);
- tagCloud.setOptions(logScaleTest.options);
- }, 300);
-
- tagCloud.on('renderComplete', function onRender() {
- if (counter > 0) {
- reject('Should not get multiple render events');
- }
- counter += 1;
- resolve(true);
- });
- });
- });
-
- afterEach(teardownDOM);
-
- test(
- 'completeness should be ok',
- handleExpectedBlip(() => {
- expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.COMPLETE);
- })
- );
- test(
- 'positions should be ok',
- handleExpectedBlip(() => {
- const textElements = domNode.querySelectorAll('text');
- verifyTagProperties(logScaleTest.expected, textElements, tagCloud);
- })
- );
- });
-
- describe('should show correct data when state-updates are interleaved with resize event', () => {
- beforeEach(async () => {
- tagCloud = new TagCloud(domNode, colorScale);
- tagCloud.setData(logScaleTest.data);
- tagCloud.setOptions(logScaleTest.options);
-
- await delay(1000); //let layout run
-
- SVGElementGetBBoxSpyInstance.mockRestore();
- SVGElementGetBBoxSpyInstance = setSVGElementGetBBox(600, 600);
-
- tagCloud.resize(); //triggers new layout
- setTimeout(() => {
- //change the options at the very end too
- tagCloud.setData(baseTest.data);
- tagCloud.setOptions(baseTest.options);
- }, 200);
- await fromNode((cb) => tagCloud.once('renderComplete', cb));
- });
-
- afterEach(teardownDOM);
-
- test(
- 'completeness should be ok',
- handleExpectedBlip(() => {
- expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.COMPLETE);
- })
- );
- test(
- 'positions should be ok',
- handleExpectedBlip(() => {
- const textElements = domNode.querySelectorAll('text');
- verifyTagProperties(baseTest.expected, textElements, tagCloud);
- })
- );
- });
-
- describe(`should not put elements in view when container is too small`, () => {
- beforeEach(async () => {
- tagCloud = new TagCloud(domNode, colorScale);
- tagCloud.setData(baseTest.data);
- tagCloud.setOptions(baseTest.options);
- await fromNode((cb) => tagCloud.once('renderComplete', cb));
- });
-
- afterEach(teardownDOM);
-
- test('completeness should not be ok', () => {
- expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.INCOMPLETE);
- });
- test('positions should not be ok', () => {
- const textElements = domNode.querySelectorAll('text');
- for (let i = 0; i < textElements; i++) {
- const bbox = textElements[i].getBoundingClientRect();
- verifyBbox(bbox, false, tagCloud);
- }
- });
- });
-
- describe(`tags should fit after making container bigger`, () => {
- beforeEach(async () => {
- tagCloud = new TagCloud(domNode, colorScale);
- tagCloud.setData(baseTest.data);
- tagCloud.setOptions(baseTest.options);
- await fromNode((cb) => tagCloud.once('renderComplete', cb));
-
- //make bigger
- tagCloud._size = [600, 600];
- tagCloud.resize();
- await fromNode((cb) => tagCloud.once('renderComplete', cb));
- });
-
- afterEach(teardownDOM);
-
- test(
- 'completeness should be ok',
- handleExpectedBlip(() => {
- expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.COMPLETE);
- })
- );
- });
-
- describe(`tags should no longer fit after making container smaller`, () => {
- beforeEach(async () => {
- tagCloud = new TagCloud(domNode, colorScale);
- tagCloud.setData(baseTest.data);
- tagCloud.setOptions(baseTest.options);
- await fromNode((cb) => tagCloud.once('renderComplete', cb));
-
- //make smaller
- tagCloud._size = [];
- tagCloud.resize();
- await fromNode((cb) => tagCloud.once('renderComplete', cb));
- });
-
- afterEach(teardownDOM);
-
- test('completeness should not be ok', () => {
- expect(tagCloud.getStatus()).toEqual(TagCloud.STATUS.INCOMPLETE);
- });
- });
-
- describe('tagcloudscreenshot', () => {
- afterEach(teardownDOM);
-
- test('should render simple image', async () => {
- tagCloud = new TagCloud(domNode, colorScale);
- tagCloud.setData(baseTest.data);
- tagCloud.setOptions(baseTest.options);
-
- await fromNode((cb) => tagCloud.once('renderComplete', cb));
-
- expect(domNode.innerHTML).toMatchSnapshot();
- });
- });
-
- function verifyTagProperties(expectedValues, actualElements, tagCloud) {
- expect(actualElements.length).toEqual(expectedValues.length);
- expectedValues.forEach((test, index) => {
- try {
- expect(actualElements[index].style.fontSize).toEqual(test.fontSize);
- } catch (e) {
- throw new Error('fontsize is not correct: ' + e.message);
- }
- try {
- expect(actualElements[index].innerHTML).toEqual(test.text);
- } catch (e) {
- throw new Error('fontsize is not correct: ' + e.message);
- }
- isInsideContainer(actualElements[index], tagCloud);
- });
- }
-
- function isInsideContainer(actualElement, tagCloud) {
- const bbox = actualElement.getBoundingClientRect();
- verifyBbox(bbox, true, tagCloud);
- }
-
- function verifyBbox(bbox, shouldBeInside, tagCloud) {
- const message = ` | bbox-of-tag: ${JSON.stringify([
- bbox.left,
- bbox.top,
- bbox.right,
- bbox.bottom,
- ])} vs
- bbox-of-container: ${domNode.offsetWidth},${domNode.offsetHeight}
- debugInfo: ${JSON.stringify(tagCloud.getDebugInfo())}`;
-
- try {
- expect(bbox.top >= 0 && bbox.top <= domNode.offsetHeight).toBe(shouldBeInside);
- } catch (e) {
- throw new Error(
- 'top boundary of tag should have been ' + (shouldBeInside ? 'inside' : 'outside') + message
- );
- }
- try {
- expect(bbox.bottom >= 0 && bbox.bottom <= domNode.offsetHeight).toBe(shouldBeInside);
- } catch (e) {
- throw new Error(
- 'bottom boundary of tag should have been ' +
- (shouldBeInside ? 'inside' : 'outside') +
- message
- );
- }
- try {
- expect(bbox.left >= 0 && bbox.left <= domNode.offsetWidth).toBe(shouldBeInside);
- } catch (e) {
- throw new Error(
- 'left boundary of tag should have been ' + (shouldBeInside ? 'inside' : 'outside') + message
- );
- }
- try {
- expect(bbox.right >= 0 && bbox.right <= domNode.offsetWidth).toBe(shouldBeInside);
- } catch (e) {
- throw new Error(
- 'right boundary of tag should have been ' +
- (shouldBeInside ? 'inside' : 'outside') +
- message
- );
- }
- }
-
- /**
- * In CI, this entire suite "blips" about 1/5 times.
- * This blip causes the majority of these tests fail for the exact same reason: One tag is centered inside the container,
- * while the others are moved out.
- * This has not been reproduced locally yet.
- * It may be an issue with the 3rd party d3-cloud that snags.
- *
- * The test suite should continue to catch reliably catch regressions of other sorts: unexpected and other uncaught errors,
- * scaling issues, ordering issues
- *
- */
- function shouldAssert() {
- const debugInfo = tagCloud.getDebugInfo();
- const count = debugInfo.positions.length;
- const largest = debugInfo.positions.pop(); //test suite puts largest tag at the end.
-
- const centered = largest[1] === 0 && largest[2] === 0;
- const halfWidth = debugInfo.size.width / 2;
- const halfHeight = debugInfo.size.height / 2;
- const inside = debugInfo.positions.filter((position) => {
- const x = position.x + halfWidth;
- const y = position.y + halfHeight;
- return 0 <= x && x <= debugInfo.size.width && 0 <= y && y <= debugInfo.size.height;
- });
-
- return centered && inside.length === count - 1;
- }
-
- function handleExpectedBlip(assertion) {
- return () => {
- if (!shouldAssert()) {
- return;
- }
- assertion();
- };
- }
-});
diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.test.tsx b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.test.tsx
new file mode 100644
index 0000000000000..b4d4e70d5ffe3
--- /dev/null
+++ b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.test.tsx
@@ -0,0 +1,150 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import React from 'react';
+import { Wordcloud, Settings } from '@elastic/charts';
+import { chartPluginMock } from '../../../charts/public/mocks';
+import type { Datatable } from '../../../expressions/public';
+import { mount } from 'enzyme';
+import { findTestSubject } from '@elastic/eui/lib/test';
+import TagCloudChart, { TagCloudChartProps } from './tag_cloud_chart';
+import { TagCloudVisParams } from '../types';
+
+jest.mock('../services', () => ({
+ getFormatService: jest.fn(() => {
+ return {
+ deserialize: jest.fn(),
+ };
+ }),
+}));
+
+const palettesRegistry = chartPluginMock.createPaletteRegistry();
+const visData = ({
+ columns: [
+ {
+ id: 'col-0',
+ name: 'geo.dest: Descending',
+ },
+ {
+ id: 'col-1',
+ name: 'Count',
+ },
+ ],
+ rows: [
+ { 'col-0': 'CN', 'col-1': 26 },
+ { 'col-0': 'IN', 'col-1': 17 },
+ { 'col-0': 'US', 'col-1': 6 },
+ { 'col-0': 'DE', 'col-1': 4 },
+ { 'col-0': 'BR', 'col-1': 3 },
+ ],
+} as unknown) as Datatable;
+
+const visParams = {
+ bucket: { accessor: 0, format: {} },
+ metric: { accessor: 1, format: {} },
+ scale: 'linear',
+ orientation: 'single',
+ palette: {
+ type: 'palette',
+ name: 'default',
+ },
+ minFontSize: 12,
+ maxFontSize: 70,
+ showLabel: true,
+} as TagCloudVisParams;
+
+describe('TagCloudChart', function () {
+ let wrapperProps: TagCloudChartProps;
+
+ beforeAll(() => {
+ wrapperProps = {
+ visData,
+ visParams,
+ palettesRegistry,
+ fireEvent: jest.fn(),
+ renderComplete: jest.fn(),
+ syncColors: false,
+ visType: 'tagcloud',
+ };
+ });
+
+ it('renders the Wordcloud component', async () => {
+ const component = mount( );
+ expect(component.find(Wordcloud).length).toBe(1);
+ });
+
+ it('renders the label correctly', async () => {
+ const component = mount( );
+ const label = findTestSubject(component, 'tagCloudLabel');
+ expect(label.text()).toEqual('geo.dest: Descending - Count');
+ });
+
+ it('not renders the label if showLabel setting is off', async () => {
+ const newVisParams = { ...visParams, showLabel: false };
+ const newProps = { ...wrapperProps, visParams: newVisParams };
+ const component = mount( );
+ const label = findTestSubject(component, 'tagCloudLabel');
+ expect(label.length).toBe(0);
+ });
+
+ it('receives the data on the correct format', () => {
+ const component = mount( );
+ expect(component.find(Wordcloud).prop('data')).toStrictEqual([
+ {
+ color: 'black',
+ text: 'CN',
+ weight: 1,
+ },
+ {
+ color: 'black',
+ text: 'IN',
+ weight: 0.6086956521739131,
+ },
+ {
+ color: 'black',
+ text: 'US',
+ weight: 0.13043478260869565,
+ },
+ {
+ color: 'black',
+ text: 'DE',
+ weight: 0.043478260869565216,
+ },
+ {
+ color: 'black',
+ text: 'BR',
+ weight: 0,
+ },
+ ]);
+ });
+
+ it('sets the angles correctly', async () => {
+ const newVisParams = { ...visParams, orientation: 'right angled' } as TagCloudVisParams;
+ const newProps = { ...wrapperProps, visParams: newVisParams };
+ const component = mount( );
+ expect(component.find(Wordcloud).prop('endAngle')).toBe(90);
+ expect(component.find(Wordcloud).prop('angleCount')).toBe(2);
+ });
+
+ it('calls filter callback', () => {
+ const component = mount( );
+ component.find(Settings).prop('onElementClick')!([
+ [
+ {
+ text: 'BR',
+ weight: 0.17391304347826086,
+ color: '#d36086',
+ },
+ {
+ specId: 'tagCloud',
+ key: 'tagCloud',
+ },
+ ],
+ ]);
+ expect(wrapperProps.fireEvent).toHaveBeenCalled();
+ });
+});
diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx
index f668e22815b60..b89fe2fa90ede 100644
--- a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx
+++ b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx
@@ -6,64 +6,225 @@
* Side Public License, v 1.
*/
-import React, { useEffect, useMemo, useRef } from 'react';
-import { EuiResizeObserver } from '@elastic/eui';
+import React, { useCallback, useState, useMemo } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
import { throttle } from 'lodash';
-
-import { TagCloudVisDependencies } from '../plugin';
+import { EuiIconTip, EuiResizeObserver } from '@elastic/eui';
+import { Chart, Settings, Wordcloud, RenderChangeListener } from '@elastic/charts';
+import type { PaletteRegistry } from '../../../charts/public';
+import type { IInterpreterRenderHandlers } from '../../../expressions/public';
+import { getFormatService } from '../services';
import { TagCloudVisRenderValue } from '../tag_cloud_fn';
-// @ts-ignore
-import { TagCloudVisualization } from './tag_cloud_visualization';
import './tag_cloud.scss';
-type TagCloudChartProps = TagCloudVisDependencies &
- TagCloudVisRenderValue & {
- fireEvent: (event: any) => void;
- renderComplete: () => void;
- };
+const MAX_TAG_COUNT = 200;
+
+export type TagCloudChartProps = TagCloudVisRenderValue & {
+ fireEvent: IInterpreterRenderHandlers['event'];
+ renderComplete: IInterpreterRenderHandlers['done'];
+ palettesRegistry: PaletteRegistry;
+};
+
+const calculateWeight = (value: number, x1: number, y1: number, x2: number, y2: number) =>
+ ((value - x1) * (y2 - x2)) / (y1 - x1) + x2;
+
+const getColor = (
+ palettes: PaletteRegistry,
+ activePalette: string,
+ text: string,
+ values: string[],
+ syncColors: boolean
+) => {
+ return palettes?.get(activePalette).getCategoricalColor(
+ [
+ {
+ name: text,
+ rankAtDepth: values.length ? values.findIndex((name) => name === text) : 0,
+ totalSeriesAtDepth: values.length || 1,
+ },
+ ],
+ {
+ maxDepth: 1,
+ totalSeries: values.length || 1,
+ behindText: false,
+ syncColors,
+ }
+ );
+};
+
+const ORIENTATIONS = {
+ single: {
+ endAngle: 0,
+ angleCount: 360,
+ },
+ 'right angled': {
+ endAngle: 90,
+ angleCount: 2,
+ },
+ multiple: {
+ endAngle: -90,
+ angleCount: 12,
+ },
+};
export const TagCloudChart = ({
- colors,
visData,
visParams,
+ palettesRegistry,
fireEvent,
renderComplete,
+ syncColors,
}: TagCloudChartProps) => {
- const chartDiv = useRef(null);
- const visController = useRef(null);
+ const [warning, setWarning] = useState(false);
+ const { bucket, metric, scale, palette, showLabel, orientation } = visParams;
+ const bucketFormatter = bucket ? getFormatService().deserialize(bucket.format) : null;
- useEffect(() => {
- if (chartDiv.current) {
- visController.current = new TagCloudVisualization(chartDiv.current, colors, fireEvent);
- }
- return () => {
- visController.current.destroy();
- visController.current = null;
- };
- }, [colors, fireEvent]);
-
- useEffect(() => {
- if (visController.current) {
- visController.current.render(visData, visParams).then(renderComplete);
- }
- }, [visData, visParams, renderComplete]);
+ const tagCloudData = useMemo(() => {
+ const tagColumn = bucket ? visData.columns[bucket.accessor].id : -1;
+ const metricColumn = visData.columns[metric.accessor]?.id;
+
+ const metrics = visData.rows.map((row) => row[metricColumn]);
+ const values = bucket ? visData.rows.map((row) => row[tagColumn]) : [];
+ const maxValue = Math.max(...metrics);
+ const minValue = Math.min(...metrics);
+
+ return visData.rows.map((row) => {
+ const tag = row[tagColumn] === undefined ? 'all' : row[tagColumn];
+ return {
+ text: (bucketFormatter ? bucketFormatter.convert(tag, 'text') : tag) as string,
+ weight:
+ tag === 'all' || visData.rows.length <= 1
+ ? 1
+ : calculateWeight(row[metricColumn], minValue, maxValue, 0, 1) || 0,
+ color: getColor(palettesRegistry, palette.name, tag, values, syncColors) || 'rgba(0,0,0,0)',
+ };
+ });
+ }, [
+ bucket,
+ bucketFormatter,
+ metric.accessor,
+ palette.name,
+ palettesRegistry,
+ syncColors,
+ visData.columns,
+ visData.rows,
+ ]);
+
+ const label = bucket
+ ? `${visData.columns[bucket.accessor].name} - ${visData.columns[metric.accessor].name}`
+ : '';
+
+ const onRenderChange = useCallback(
+ (isRendered) => {
+ if (isRendered) {
+ renderComplete();
+ }
+ },
+ [renderComplete]
+ );
- const updateChartSize = useMemo(
+ const updateChart = useMemo(
() =>
throttle(() => {
- if (visController.current) {
- visController.current.render(visData, visParams).then(renderComplete);
- }
+ setWarning(false);
}, 300),
- [renderComplete, visData, visParams]
+ []
+ );
+
+ const handleWordClick = useCallback(
+ (d) => {
+ if (!bucket) {
+ return;
+ }
+ const termsBucket = visData.columns[bucket.accessor];
+ const clickedValue = d[0][0].text;
+
+ const rowIndex = visData.rows.findIndex((row) => {
+ const formattedValue = bucketFormatter
+ ? bucketFormatter.convert(row[termsBucket.id], 'text')
+ : row[termsBucket.id];
+ return formattedValue === clickedValue;
+ });
+
+ if (rowIndex < 0) {
+ return;
+ }
+
+ fireEvent({
+ name: 'filterBucket',
+ data: {
+ data: [
+ {
+ table: visData,
+ column: bucket.accessor,
+ row: rowIndex,
+ },
+ ],
+ },
+ });
+ },
+ [bucket, bucketFormatter, fireEvent, visData]
);
return (
-
+
{(resizeRef) => (
-
-
+
+
+
+ {
+ setWarning(true);
+ }}
+ />
+
+ {label && showLabel && (
+
+ {label}
+
+ )}
+ {warning && (
+
+
+ }
+ />
+
+ )}
+ {tagCloudData.length > MAX_TAG_COUNT && (
+
+
+ }
+ />
+
+ )}
)}
diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx
index d5e005a638680..6682799a8038a 100644
--- a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx
+++ b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx
@@ -6,16 +6,22 @@
* Side Public License, v 1.
*/
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import { EuiPanel } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { VisEditorOptionsProps } from 'src/plugins/visualizations/public';
-import { SelectOption, SwitchOption } from '../../../vis_default_editor/public';
+import type { PaletteRegistry } from '../../../charts/public';
+import { VisEditorOptionsProps } from '../../../visualizations/public';
+import { SelectOption, SwitchOption, PalettePicker } from '../../../vis_default_editor/public';
import { ValidatedDualRange } from '../../../kibana_react/public';
-import { TagCloudVisParams } from '../types';
+import { TagCloudVisParams, TagCloudTypeProps } from '../types';
import { collections } from './collections';
-function TagCloudOptions({ stateParams, setValue }: VisEditorOptionsProps
) {
+interface TagCloudOptionsProps
+ extends VisEditorOptionsProps,
+ TagCloudTypeProps {}
+
+function TagCloudOptions({ stateParams, setValue, palettes }: TagCloudOptionsProps) {
+ const [palettesRegistry, setPalettesRegistry] = useState(undefined);
const handleFontSizeChange = ([minFontSize, maxFontSize]: [string | number, string | number]) => {
setValue('minFontSize', Number(minFontSize));
setValue('maxFontSize', Number(maxFontSize));
@@ -24,6 +30,14 @@ function TagCloudOptions({ stateParams, setValue }: VisEditorOptionsProps {
+ const fetchPalettes = async () => {
+ const palettesService = await palettes?.getPalettes();
+ setPalettesRegistry(palettesService);
+ };
+ fetchPalettes();
+ }, [palettes]);
+
return (
+ {palettesRegistry && (
+ {
+ setValue(paramName, value);
+ }}
+ />
+ )}
+
{
- if (!this._visParams.bucket) {
- return;
- }
-
- fireEvent({
- name: 'filterBucket',
- data: {
- data: [
- {
- table: event.meta.data,
- column: 0,
- row: event.meta.rowIndex,
- },
- ],
- },
- });
- });
- this._renderComplete$ = Rx.fromEvent(this._tagCloud, 'renderComplete');
-
- this._feedbackNode = document.createElement('div');
- this._containerNode.appendChild(this._feedbackNode);
- this._feedbackMessage = React.createRef();
- render(
-
-
- ,
- this._feedbackNode
- );
-
- this._labelNode = document.createElement('div');
- this._containerNode.appendChild(this._labelNode);
- this._label = React.createRef();
- render( , this._labelNode);
- }
-
- async render(data, visParams) {
- this._updateParams(visParams);
- this._updateData(data);
- this._resize();
-
- await this._renderComplete$.pipe(take(1)).toPromise();
-
- if (data && data.columns.length !== 2 && this._feedbackMessage.current) {
- this._feedbackMessage.current.setState({
- shouldShowTruncate: false,
- shouldShowIncomplete: false,
- });
- return;
- }
-
- if (data && this._label.current) {
- this._label.current.setState({
- label: `${data.columns[0].name} - ${data.columns[1].name}`,
- shouldShowLabel: visParams.showLabel,
- });
- }
-
- if (this._feedbackMessage.current) {
- this._feedbackMessage.current.setState({
- shouldShowTruncate: this._truncated,
- shouldShowIncomplete: this._tagCloud.getStatus() === TagCloud.STATUS.INCOMPLETE,
- });
- }
- }
-
- destroy() {
- this._tagCloud.destroy();
- unmountComponentAtNode(this._feedbackNode);
- unmountComponentAtNode(this._labelNode);
- }
-
- _updateData(data) {
- if (!data || !data.rows.length) {
- this._tagCloud.setData([]);
- return;
- }
-
- const bucket = this._visParams.bucket;
- const metric = this._visParams.metric;
- const bucketFormatter = bucket ? getFormatService().deserialize(bucket.format) : null;
- const tagColumn = bucket ? data.columns[bucket.accessor].id : -1;
- const metricColumn = data.columns[metric.accessor].id;
- const tags = data.rows.map((row, rowIndex) => {
- const tag = row[tagColumn] === undefined ? 'all' : row[tagColumn];
- const metric = row[metricColumn];
- return {
- displayText: bucketFormatter ? bucketFormatter.convert(tag, 'text') : tag,
- rawText: tag,
- value: metric,
- meta: {
- data: data,
- rowIndex: rowIndex,
- },
- };
- });
-
- if (tags.length > MAX_TAG_COUNT) {
- tags.length = MAX_TAG_COUNT;
- this._truncated = true;
- } else {
- this._truncated = false;
- }
-
- this._tagCloud.setData(tags);
- }
-
- _updateParams(visParams) {
- this._visParams = visParams;
- this._tagCloud.setOptions(visParams);
- }
-
- _resize() {
- this._tagCloud.resize();
- }
-}
diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.test.js b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.test.js
deleted file mode 100644
index 26da8b7e72dd1..0000000000000
--- a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.test.js
+++ /dev/null
@@ -1,128 +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 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import 'jest-canvas-mock';
-
-import { TagCloudVisualization } from './tag_cloud_visualization';
-import { setFormatService } from '../services';
-import { dataPluginMock } from '../../../data/public/mocks';
-import { setHTMLElementOffset, setSVGElementGetBBox } from '@kbn/test/jest';
-
-const seedColors = ['#00a69b', '#57c17b', '#6f87d8', '#663db8', '#bc52bc', '#9e3533', '#daa05d'];
-
-describe('TagCloudVisualizationTest', () => {
- let domNode;
- let visParams;
- let SVGElementGetBBoxSpyInstance;
- let HTMLElementOffsetMockInstance;
-
- const dummyTableGroup = {
- columns: [
- {
- id: 'col-0',
- title: 'geo.dest: Descending',
- },
- {
- id: 'col-1',
- title: 'Count',
- },
- ],
- rows: [
- { 'col-0': 'CN', 'col-1': 26 },
- { 'col-0': 'IN', 'col-1': 17 },
- { 'col-0': 'US', 'col-1': 6 },
- { 'col-0': 'DE', 'col-1': 4 },
- { 'col-0': 'BR', 'col-1': 3 },
- ],
- };
-
- const originTransformSVGElement = window.SVGElement.prototype.transform;
-
- beforeAll(() => {
- setFormatService(dataPluginMock.createStartContract().fieldFormats);
- Object.defineProperties(window.SVGElement.prototype, {
- transform: {
- get: () => ({
- baseVal: {
- consolidate: () => {},
- },
- }),
- configurable: true,
- },
- });
- });
-
- afterAll(() => {
- SVGElementGetBBoxSpyInstance.mockRestore();
- HTMLElementOffsetMockInstance.mockRestore();
- window.SVGElement.prototype.transform = originTransformSVGElement;
- });
-
- describe('TagCloudVisualization - basics', () => {
- beforeEach(async () => {
- setupDOM(512, 512);
-
- visParams = {
- bucket: { accessor: 0, format: {} },
- metric: { accessor: 0, format: {} },
- scale: 'linear',
- orientation: 'single',
- };
- });
-
- test('simple draw', async () => {
- const tagcloudVisualization = new TagCloudVisualization(domNode, {
- seedColors,
- });
-
- await tagcloudVisualization.render(dummyTableGroup, visParams);
-
- const svgNode = domNode.querySelector('svg');
- expect(svgNode.outerHTML).toMatchSnapshot();
- });
-
- test('with resize', async () => {
- const tagcloudVisualization = new TagCloudVisualization(domNode, {
- seedColors,
- });
- await tagcloudVisualization.render(dummyTableGroup, visParams);
-
- await tagcloudVisualization.render(dummyTableGroup, visParams);
-
- const svgNode = domNode.querySelector('svg');
- expect(svgNode.outerHTML).toMatchSnapshot();
- });
-
- test('with param change', async function () {
- const tagcloudVisualization = new TagCloudVisualization(domNode, {
- seedColors,
- });
- await tagcloudVisualization.render(dummyTableGroup, visParams);
-
- SVGElementGetBBoxSpyInstance.mockRestore();
- SVGElementGetBBoxSpyInstance = setSVGElementGetBBox(256, 368);
-
- HTMLElementOffsetMockInstance.mockRestore();
- HTMLElementOffsetMockInstance = setHTMLElementOffset(256, 386);
-
- visParams.orientation = 'right angled';
- visParams.minFontSize = 70;
- await tagcloudVisualization.render(dummyTableGroup, visParams);
-
- const svgNode = domNode.querySelector('svg');
- expect(svgNode.outerHTML).toMatchSnapshot();
- });
- });
-
- function setupDOM(width, height) {
- domNode = document.createElement('div');
-
- HTMLElementOffsetMockInstance = setHTMLElementOffset(width, height);
- SVGElementGetBBoxSpyInstance = setSVGElementGetBBox(width, height);
- }
-});
diff --git a/src/plugins/vis_type_tagcloud/public/plugin.ts b/src/plugins/vis_type_tagcloud/public/plugin.ts
index a48e0726e45fe..b2414762f6e47 100644
--- a/src/plugins/vis_type_tagcloud/public/plugin.ts
+++ b/src/plugins/vis_type_tagcloud/public/plugin.ts
@@ -12,7 +12,7 @@ import { VisualizationsSetup } from '../../visualizations/public';
import { ChartsPluginSetup } from '../../charts/public';
import { createTagCloudFn } from './tag_cloud_fn';
-import { tagCloudVisTypeDefinition } from './tag_cloud_type';
+import { getTagCloudVisTypeDefinition } from './tag_cloud_type';
import { DataPublicPluginStart } from '../../data/public';
import { setFormatService } from './services';
import { ConfigSchema } from '../config';
@@ -27,7 +27,7 @@ export interface TagCloudPluginSetupDependencies {
/** @internal */
export interface TagCloudVisDependencies {
- colors: ChartsPluginSetup['legacyColors'];
+ palettes: ChartsPluginSetup['palettes'];
}
/** @internal */
@@ -48,11 +48,15 @@ export class TagCloudPlugin implements Plugin {
{ expressions, visualizations, charts }: TagCloudPluginSetupDependencies
) {
const visualizationDependencies: TagCloudVisDependencies = {
- colors: charts.legacyColors,
+ palettes: charts.palettes,
};
expressions.registerFunction(createTagCloudFn);
expressions.registerRenderer(getTagCloudVisRenderer(visualizationDependencies));
- visualizations.createBaseVisualization(tagCloudVisTypeDefinition);
+ visualizations.createBaseVisualization(
+ getTagCloudVisTypeDefinition({
+ palettes: charts.palettes,
+ })
+ );
}
public start(core: CoreStart, { data }: TagCloudVisPluginStartDependencies) {
diff --git a/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.test.ts b/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.test.ts
index 0b6a224eee7b5..5dcdffffde01d 100644
--- a/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.test.ts
+++ b/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.test.ts
@@ -24,6 +24,7 @@ describe('interpreter/functions#tagcloud', () => {
maxFontSize: 72,
showLabel: true,
metric: { accessor: 0, format: { id: 'number' } },
+ bucket: { accessor: 1, format: { id: 'number' } },
};
it('returns an object with the correct structure', () => {
diff --git a/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.ts b/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.ts
index d831ba8c760df..17855db9150b5 100644
--- a/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.ts
+++ b/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.ts
@@ -9,19 +9,19 @@
import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public';
-import { TagCloudVisParams } from './types';
+import { TagCloudVisParams, TagCloudVisConfig } from './types';
const name = 'tagcloud';
-interface Arguments extends TagCloudVisParams {
- metric: any; // these aren't typed yet
- bucket?: any; // these aren't typed yet
+interface Arguments extends TagCloudVisConfig {
+ palette: string;
}
export interface TagCloudVisRenderValue {
visType: typeof name;
visData: Datatable;
- visParams: Arguments;
+ visParams: TagCloudVisParams;
+ syncColors: boolean;
}
export type TagcloudExpressionFunctionDefinition = ExpressionFunctionDefinition<
@@ -70,6 +70,13 @@ export const createTagCloudFn = (): TagcloudExpressionFunctionDefinition => ({
default: true,
help: '',
},
+ palette: {
+ types: ['string'],
+ help: i18n.translate('visTypeTagCloud.function.paletteHelpText', {
+ defaultMessage: 'Defines the chart palette name',
+ }),
+ default: 'default',
+ },
metric: {
types: ['vis_dimension'],
help: i18n.translate('visTypeTagCloud.function.metric.help', {
@@ -92,11 +99,14 @@ export const createTagCloudFn = (): TagcloudExpressionFunctionDefinition => ({
maxFontSize: args.maxFontSize,
showLabel: args.showLabel,
metric: args.metric,
- } as Arguments;
-
- if (args.bucket !== undefined) {
- visParams.bucket = args.bucket;
- }
+ ...(args.bucket && {
+ bucket: args.bucket,
+ }),
+ palette: {
+ type: 'palette',
+ name: args.palette,
+ },
+ } as TagCloudVisParams;
if (handlers?.inspectorAdapters?.tables) {
handlers.inspectorAdapters.tables.logDatatable('default', input);
@@ -108,6 +118,7 @@ export const createTagCloudFn = (): TagcloudExpressionFunctionDefinition => ({
visData: input,
visType: name,
visParams,
+ syncColors: handlers?.isSyncColorsEnabled?.() ?? false,
},
};
},
diff --git a/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts b/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts
index 960122c178caa..b3ab5cd3d7af7 100644
--- a/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts
+++ b/src/plugins/vis_type_tagcloud/public/tag_cloud_type.ts
@@ -10,10 +10,11 @@ import { i18n } from '@kbn/i18n';
import { AggGroupNames } from '../../data/public';
import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public';
-import { TagCloudOptions } from './components/tag_cloud_options';
+import { getTagCloudOptions } from './components/get_tag_cloud_options';
import { toExpressionAst } from './to_ast';
+import { TagCloudVisDependencies } from './plugin';
-export const tagCloudVisTypeDefinition = {
+export const getTagCloudVisTypeDefinition = ({ palettes }: TagCloudVisDependencies) => ({
name: 'tagcloud',
title: i18n.translate('visTypeTagCloud.vis.tagCloudTitle', { defaultMessage: 'Tag cloud' }),
icon: 'visTagCloud',
@@ -30,11 +31,17 @@ export const tagCloudVisTypeDefinition = {
minFontSize: 18,
maxFontSize: 72,
showLabel: true,
+ palette: {
+ name: 'default',
+ type: 'palette',
+ },
},
},
toExpressionAst,
editorConfig: {
- optionsTemplate: TagCloudOptions,
+ optionsTemplate: getTagCloudOptions({
+ palettes,
+ }),
schemas: [
{
group: AggGroupNames.Metrics,
@@ -69,4 +76,4 @@ export const tagCloudVisTypeDefinition = {
],
},
requiresSearch: true,
-};
+});
diff --git a/src/plugins/vis_type_tagcloud/public/tag_cloud_vis_renderer.tsx b/src/plugins/vis_type_tagcloud/public/tag_cloud_vis_renderer.tsx
index 3f05c35ab1dbb..279bfdfffee67 100644
--- a/src/plugins/vis_type_tagcloud/public/tag_cloud_vis_renderer.tsx
+++ b/src/plugins/vis_type_tagcloud/public/tag_cloud_vis_renderer.tsx
@@ -8,6 +8,7 @@
import React, { lazy } from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
+import { I18nProvider } from '@kbn/i18n/react';
import { VisualizationContainer } from '../../visualizations/public';
import { ExpressionRenderDefinition } from '../../expressions/common/expression_renderers';
@@ -18,7 +19,7 @@ const TagCloudChart = lazy(() => import('./components/tag_cloud_chart'));
export const getTagCloudVisRenderer: (
deps: TagCloudVisDependencies
-) => ExpressionRenderDefinition = ({ colors }) => ({
+) => ExpressionRenderDefinition = ({ palettes }) => ({
name: 'tagloud_vis',
displayName: 'Tag Cloud visualization',
reuseDomNode: true,
@@ -26,16 +27,20 @@ export const getTagCloudVisRenderer: (
handlers.onDestroy(() => {
unmountComponentAtNode(domNode);
});
+ const palettesRegistry = await palettes.getPalettes();
render(
-
-
- ,
+
+
+
+
+ ,
domNode
);
},
diff --git a/src/plugins/vis_type_tagcloud/public/to_ast.test.ts b/src/plugins/vis_type_tagcloud/public/to_ast.test.ts
index 186f621f583d7..4da9c525a4f93 100644
--- a/src/plugins/vis_type_tagcloud/public/to_ast.test.ts
+++ b/src/plugins/vis_type_tagcloud/public/to_ast.test.ts
@@ -66,6 +66,11 @@ describe('tagcloud vis toExpressionAst function', () => {
minFontSize: 5,
maxFontSize: 15,
showLabel: true,
+ palette: {
+ type: 'palette',
+ name: 'default',
+ },
+ metric: { accessor: 0, format: { id: 'number' } },
};
const actual = toExpressionAst(vis, {} as any);
expect(actual).toMatchSnapshot();
diff --git a/src/plugins/vis_type_tagcloud/public/to_ast.ts b/src/plugins/vis_type_tagcloud/public/to_ast.ts
index 38f2ef9271b3d..8a2fb4e843973 100644
--- a/src/plugins/vis_type_tagcloud/public/to_ast.ts
+++ b/src/plugins/vis_type_tagcloud/public/to_ast.ts
@@ -39,7 +39,7 @@ export const toExpressionAst: VisToExpressionAst = (vis, para
});
const schemas = getVisSchemas(vis, params);
- const { scale, orientation, minFontSize, maxFontSize, showLabel } = vis.params;
+ const { scale, orientation, minFontSize, maxFontSize, showLabel, palette } = vis.params;
const tagcloud = buildExpressionFunction('tagcloud', {
scale,
@@ -48,6 +48,7 @@ export const toExpressionAst: VisToExpressionAst = (vis, para
maxFontSize,
showLabel,
metric: prepareDimension(schemas.metric[0]),
+ palette: palette?.name,
});
if (schemas.segment) {
diff --git a/src/plugins/vis_type_tagcloud/public/types.ts b/src/plugins/vis_type_tagcloud/public/types.ts
index d1c63d8c634bb..7105476670693 100644
--- a/src/plugins/vis_type_tagcloud/public/types.ts
+++ b/src/plugins/vis_type_tagcloud/public/types.ts
@@ -5,11 +5,37 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
+import type { ChartsPluginSetup, PaletteOutput } from '../../charts/public';
+import type { SerializedFieldFormat } from '../../expressions/public';
+import { ExpressionValueVisDimension } from '../../visualizations/public';
-export interface TagCloudVisParams {
+interface Dimension {
+ accessor: number;
+ format: {
+ id?: string;
+ params?: SerializedFieldFormat;
+ };
+}
+
+interface TagCloudCommonParams {
scale: 'linear' | 'log' | 'square root';
orientation: 'single' | 'right angled' | 'multiple';
minFontSize: number;
maxFontSize: number;
showLabel: boolean;
}
+
+export interface TagCloudVisConfig extends TagCloudCommonParams {
+ metric: ExpressionValueVisDimension;
+ bucket?: ExpressionValueVisDimension;
+}
+
+export interface TagCloudVisParams extends TagCloudCommonParams {
+ palette: PaletteOutput;
+ metric: Dimension;
+ bucket?: Dimension;
+}
+
+export interface TagCloudTypeProps {
+ palettes: ChartsPluginSetup['palettes'];
+}
diff --git a/src/plugins/visualizations/server/embeddable/visualize_embeddable_factory.ts b/src/plugins/visualizations/server/embeddable/visualize_embeddable_factory.ts
index edfd05b84dfc8..0f549584af672 100644
--- a/src/plugins/visualizations/server/embeddable/visualize_embeddable_factory.ts
+++ b/src/plugins/visualizations/server/embeddable/visualize_embeddable_factory.ts
@@ -15,6 +15,7 @@ import {
commonRemoveDefaultIndexPatternAndTimeFieldFromTSVBModel,
commonMigrateVislibPie,
commonAddEmptyValueColorRule,
+ commonMigrateTagCloud,
} from '../migrations/visualization_common_migrations';
const byValueAddSupportOfDualIndexSelectionModeInTSVB = (state: SerializableState) => {
@@ -52,6 +53,13 @@ const byValueMigrateVislibPie = (state: SerializableState) => {
};
};
+const byValueMigrateTagcloud = (state: SerializableState) => {
+ return {
+ ...state,
+ savedVis: commonMigrateTagCloud(state.savedVis),
+ };
+};
+
export const visualizeEmbeddableFactory = (): EmbeddableRegistryDefinition => {
return {
id: 'visualization',
@@ -63,7 +71,8 @@ export const visualizeEmbeddableFactory = (): EmbeddableRegistryDefinition => {
byValueHideTSVBLastValueIndicator,
byValueRemoveDefaultIndexPatternAndTimeFieldFromTSVBModel
)(state),
- '7.14.0': (state) => flow(byValueAddEmptyValueColorRule, byValueMigrateVislibPie)(state),
+ '7.14.0': (state) =>
+ flow(byValueAddEmptyValueColorRule, byValueMigrateVislibPie, byValueMigrateTagcloud)(state),
},
};
};
diff --git a/src/plugins/visualizations/server/migrations/visualization_common_migrations.ts b/src/plugins/visualizations/server/migrations/visualization_common_migrations.ts
index f5afeee0ff35e..17b1470a40062 100644
--- a/src/plugins/visualizations/server/migrations/visualization_common_migrations.ts
+++ b/src/plugins/visualizations/server/migrations/visualization_common_migrations.ts
@@ -114,3 +114,25 @@ export const commonMigrateVislibPie = (visState: any) => {
return visState;
};
+
+export const commonMigrateTagCloud = (visState: any) => {
+ if (visState && visState.type === 'tagcloud') {
+ const { params } = visState;
+ const hasPalette = params?.palette;
+
+ return {
+ ...visState,
+ params: {
+ ...visState.params,
+ ...(!hasPalette && {
+ palette: {
+ type: 'palette',
+ name: 'kibana_palette',
+ },
+ }),
+ },
+ };
+ }
+
+ return visState;
+};
diff --git a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts
index 7ee43f36c864e..7debc9412925e 100644
--- a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts
+++ b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts
@@ -2162,4 +2162,45 @@ describe('migration visualization', () => {
expect(distinctColors).toBe(true);
});
});
+
+ describe('7.14.0 update tagcloud defaults', () => {
+ const migrate = (doc: any) =>
+ visualizationSavedObjectTypeMigrations['7.14.0'](
+ doc as Parameters[0],
+ savedObjectMigrationContext
+ );
+ const getTestDoc = (hasPalette = false) => ({
+ attributes: {
+ title: 'My Vis',
+ description: 'This is my super cool vis.',
+ visState: JSON.stringify({
+ type: 'tagcloud',
+ title: '[Flights] Delay Type',
+ params: {
+ type: 'tagcloud',
+ ...(hasPalette && {
+ palette: {
+ type: 'palette',
+ name: 'default',
+ },
+ }),
+ },
+ }),
+ },
+ });
+
+ it('should decorate existing docs with the kibana legacy palette if the palette is not defined - pie', () => {
+ const migratedTestDoc = migrate(getTestDoc());
+ const { palette } = JSON.parse(migratedTestDoc.attributes.visState).params;
+
+ expect(palette.name).toEqual('kibana_palette');
+ });
+
+ it('should not overwrite the palette with the legacy one if the palette already exists in the saved object', () => {
+ const migratedTestDoc = migrate(getTestDoc(true));
+ const { palette } = JSON.parse(migratedTestDoc.attributes.visState).params;
+
+ expect(palette.name).toEqual('default');
+ });
+ });
});
diff --git a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts
index f386d9eb12091..7fb54b0425935 100644
--- a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts
+++ b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts
@@ -17,6 +17,7 @@ import {
commonRemoveDefaultIndexPatternAndTimeFieldFromTSVBModel,
commonMigrateVislibPie,
commonAddEmptyValueColorRule,
+ commonMigrateTagCloud,
} from './visualization_common_migrations';
const migrateIndexPattern: SavedObjectMigrationFn = (doc) => {
@@ -1014,6 +1015,29 @@ const migrateVislibPie: SavedObjectMigrationFn = (doc) => {
return doc;
};
+// [Tagcloud] Migrate to the new palette service
+const migrateTagCloud: SavedObjectMigrationFn = (doc) => {
+ const visStateJSON = get(doc, 'attributes.visState');
+ let visState;
+
+ if (visStateJSON) {
+ try {
+ visState = JSON.parse(visStateJSON);
+ } catch (e) {
+ // Let it go, the data is invalid and we'll leave it as is
+ }
+ const newVisState = commonMigrateTagCloud(visState);
+ return {
+ ...doc,
+ attributes: {
+ ...doc.attributes,
+ visState: JSON.stringify(newVisState),
+ },
+ };
+ }
+ return doc;
+};
+
export const visualizationSavedObjectTypeMigrations = {
/**
* We need to have this migration twice, once with a version prior to 7.0.0 once with a version
@@ -1060,5 +1084,5 @@ export const visualizationSavedObjectTypeMigrations = {
hideTSVBLastValueIndicator,
removeDefaultIndexPatternAndTimeFieldFromTSVBModel
),
- '7.14.0': flow(addEmptyValueColorRule, migrateVislibPie),
+ '7.14.0': flow(addEmptyValueColorRule, migrateVislibPie, migrateTagCloud),
};
diff --git a/test/functional/apps/visualize/_tag_cloud.ts b/test/functional/apps/visualize/_tag_cloud.ts
index a6ac324d9dc61..d18c85f3b58be 100644
--- a/test/functional/apps/visualize/_tag_cloud.ts
+++ b/test/functional/apps/visualize/_tag_cloud.ts
@@ -63,8 +63,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(data).to.eql([
'32,212,254,720',
'21,474,836,480',
- '20,401,094,656',
'19,327,352,832',
+ '20,401,094,656',
'18,253,611,008',
]);
});
@@ -91,8 +91,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(data).to.eql([
'32,212,254,720',
'21,474,836,480',
- '20,401,094,656',
'19,327,352,832',
+ '20,401,094,656',
'18,253,611,008',
]);
});
@@ -106,8 +106,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(data).to.eql([
'32,212,254,720',
'21,474,836,480',
- '20,401,094,656',
'19,327,352,832',
+ '20,401,094,656',
'18,253,611,008',
]);
});
@@ -122,7 +122,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('should show the tags and relative size', function () {
return PageObjects.tagCloud.getTextSizes().then(function (results) {
log.debug('results here ' + results);
- expect(results).to.eql(['72px', '63px', '25px', '32px', '18px']);
+ expect(results).to.eql(['72px', '63px', '32px', '25px', '18px']);
});
});
@@ -177,7 +177,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('should format tags with field formatter', async function () {
const data = await PageObjects.tagCloud.getTextTag();
log.debug(data);
- expect(data).to.eql(['30GB', '20GB', '19GB', '18GB', '17GB']);
+ expect(data).to.eql(['30GB', '20GB', '18GB', '19GB', '17GB']);
});
it('should apply filter with unformatted value', async function () {
diff --git a/test/functional/page_objects/tag_cloud_page.ts b/test/functional/page_objects/tag_cloud_page.ts
index 61e844c813df8..ce51959390a42 100644
--- a/test/functional/page_objects/tag_cloud_page.ts
+++ b/test/functional/page_objects/tag_cloud_page.ts
@@ -11,15 +11,24 @@ import { WebElementWrapper } from '../services/lib/web_element_wrapper';
export class TagCloudPageObject extends FtrService {
private readonly find = this.ctx.getService('find');
- private readonly testSubjects = this.ctx.getService('testSubjects');
private readonly header = this.ctx.getPageObject('header');
private readonly visChart = this.ctx.getPageObject('visChart');
public async selectTagCloudTag(tagDisplayText: string) {
- await this.testSubjects.click(tagDisplayText);
+ const elements = await this.find.allByCssSelector('text');
+ const targetElement = elements.find(
+ async (element) => (await element.getVisibleText()) === tagDisplayText
+ );
+ await targetElement?.click();
await this.header.waitUntilLoadingHasFinished();
}
+ public async getTextTagByElement(webElement: WebElementWrapper) {
+ await this.visChart.waitForVisualization();
+ const elements = await webElement.findAllByCssSelector('text');
+ return await Promise.all(elements.map(async (element) => await element.getVisibleText()));
+ }
+
public async getTextTag() {
await this.visChart.waitForVisualization();
const elements = await this.find.allByCssSelector('text');
diff --git a/test/functional/services/dashboard/expectations.ts b/test/functional/services/dashboard/expectations.ts
index c22eddb032cf9..0a689c0091edc 100644
--- a/test/functional/services/dashboard/expectations.ts
+++ b/test/functional/services/dashboard/expectations.ts
@@ -16,8 +16,10 @@ export class DashboardExpectService extends FtrService {
private readonly testSubjects = this.ctx.getService('testSubjects');
private readonly find = this.ctx.getService('find');
private readonly filterBar = this.ctx.getService('filterBar');
+
private readonly dashboard = this.ctx.getPageObject('dashboard');
private readonly visChart = this.ctx.getPageObject('visChart');
+ private readonly tagCloud = this.ctx.getPageObject('tagCloud');
private readonly findTimeout = 2500;
async panelCount(expectedCount: number) {
@@ -166,8 +168,9 @@ export class DashboardExpectService extends FtrService {
const tagCloudVisualizations = await this.testSubjects.findAll('tagCloudVisualization');
const matches = await Promise.all(
tagCloudVisualizations.map(async (tagCloud) => {
+ const tagCloudData = await this.tagCloud.getTextTagByElement(tagCloud);
for (let i = 0; i < values.length; i++) {
- const valueExists = await this.testSubjects.descendantExists(values[i], tagCloud);
+ const valueExists = tagCloudData.includes(values[i]);
if (!valueExists) {
return false;
}
diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_1.json b/test/interpreter_functional/snapshots/baseline/partial_test_1.json
index 14c8428c6d432..e0b62688d0662 100644
--- a/test/interpreter_functional/snapshots/baseline/partial_test_1.json
+++ b/test/interpreter_functional/snapshots/baseline/partial_test_1.json
@@ -1 +1 @@
-{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json
index 073fca760b9a2..d85444f5d3b6b 100644
--- a/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json
+++ b/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json
@@ -1 +1 @@
-{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json b/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json
index 93f8d8a27d233..2c81c9447b826 100644
--- a/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json
+++ b/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json
@@ -1 +1 @@
-{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_invalid_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_invalid_data.json
index 0c50947beca97..687b669b18e61 100644
--- a/test/interpreter_functional/snapshots/baseline/tagcloud_invalid_data.json
+++ b/test/interpreter_functional/snapshots/baseline/tagcloud_invalid_data.json
@@ -1 +1 @@
-{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[],"meta":{},"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[],"meta":{},"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json
index e8c47efdbe622..b49953f9a023b 100644
--- a/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json
+++ b/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json
@@ -1 +1 @@
-{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_options.json b/test/interpreter_functional/snapshots/baseline/tagcloud_options.json
index 38683082975f8..fc7e289dfbd3a 100644
--- a/test/interpreter_functional/snapshots/baseline/tagcloud_options.json
+++ b/test/interpreter_functional/snapshots/baseline/tagcloud_options.json
@@ -1 +1 @@
-{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","scale":"log","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"default","type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/partial_test_1.json b/test/interpreter_functional/snapshots/session/partial_test_1.json
index 14c8428c6d432..e0b62688d0662 100644
--- a/test/interpreter_functional/snapshots/session/partial_test_1.json
+++ b/test/interpreter_functional/snapshots/session/partial_test_1.json
@@ -1 +1 @@
-{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/tagcloud_all_data.json b/test/interpreter_functional/snapshots/session/tagcloud_all_data.json
index 073fca760b9a2..d85444f5d3b6b 100644
--- a/test/interpreter_functional/snapshots/session/tagcloud_all_data.json
+++ b/test/interpreter_functional/snapshots/session/tagcloud_all_data.json
@@ -1 +1 @@
-{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json b/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json
index 93f8d8a27d233..2c81c9447b826 100644
--- a/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json
+++ b/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json
@@ -1 +1 @@
-{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json b/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json
index 0c50947beca97..687b669b18e61 100644
--- a/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json
+++ b/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json
@@ -1 +1 @@
-{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[],"meta":{},"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[],"meta":{},"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json b/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json
index e8c47efdbe622..b49953f9a023b 100644
--- a/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json
+++ b/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json
@@ -1 +1 @@
-{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/test/interpreter_functional/snapshots/session/tagcloud_options.json b/test/interpreter_functional/snapshots/session/tagcloud_options.json
index 38683082975f8..fc7e289dfbd3a 100644
--- a/test/interpreter_functional/snapshots/session/tagcloud_options.json
+++ b/test/interpreter_functional/snapshots/session/tagcloud_options.json
@@ -1 +1 @@
-{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","scale":"log","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
+{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"default","type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 0e2b953389c10..a12920f72ba82 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10870,7 +10870,7 @@ d3-array@>=2.5, d3-array@^2.3.0, d3-array@^2.7.1:
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.8.0.tgz#f76e10ad47f1f4f75f33db5fc322eb9ffde5ef23"
integrity sha512-6V272gsOeg7+9pTW1jSYOR1QE37g95I3my1hBmY+vOUNHRrk9yt4OTz/gK7PMkVAVDrYYq4mq3grTiZ8iJdNIw==
-d3-cloud@1.2.5, d3-cloud@^1.2.5:
+d3-cloud@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/d3-cloud/-/d3-cloud-1.2.5.tgz#3e91564f2d27fba47fcc7d812eb5081ea24c603d"
integrity sha512-4s2hXZgvs0CoUIw31oBAGrHt9Kt/7P9Ik5HIVzISFiWkD0Ga2VLAuO/emO/z1tYIpE7KG2smB4PhMPfFMJpahw==
From d3144e1439acf2aa7166d60e5aacd3972e5e1554 Mon Sep 17 00:00:00 2001
From: Matthias Wilhelm
Date: Tue, 15 Jun 2021 08:31:12 +0200
Subject: [PATCH 26/91] [Discover] Improve functional test wording for
responsive tests(#101744)
---
test/functional/apps/discover/_doc_table.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/test/functional/apps/discover/_doc_table.ts b/test/functional/apps/discover/_doc_table.ts
index b5a40030b2601..5d7c16c3c6408 100644
--- a/test/functional/apps/discover/_doc_table.ts
+++ b/test/functional/apps/discover/_doc_table.ts
@@ -68,7 +68,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.discover.waitUntilSearchingHasFinished();
});
- it(`should load up to ${rowsHardLimit} rows when scrolling at the end of the table with `, async function () {
+ it('should load more rows when scrolling down the document table', async function () {
const initialRows = await testSubjects.findAll('docTableRow');
await testSubjects.scrollIntoView('discoverBackToTop');
// now count the rows
@@ -88,7 +88,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.discover.waitUntilSearchingHasFinished();
});
- it(`should load up to ${rowsHardLimit} rows when scrolling at the end of the table with `, async function () {
+ it('should load more rows when scrolling down the document table', async function () {
const initialRows = await testSubjects.findAll('docTableRow');
await testSubjects.scrollIntoView('discoverBackToTop');
// now count the rows
From f70542abe2a873ee6615151b5a6fccc8d6212a5e Mon Sep 17 00:00:00 2001
From: Jean-Louis Leysens
Date: Tue, 15 Jun 2021 09:26:37 +0200
Subject: [PATCH 27/91] [Fleet] Adjust add integration flow (#101714)
* Initial commit, very WIP
- added link to the integrations UI instead of add integration
in fleet
- added a new piece of route state that gets passed to integration
from fleet when browsing integrations for a policy
- when the integration is being added the page will send the user
back to the policy overview instead of back to integrations UI
* remove unnecessary agent policy clear function
* added # to path so that navigation is correctly handled
* added logic to read the forward agent policy id
* remove inline select integration package from fleet add integration
* updated toast notification
* using query parameter to pass policy id back to create policy package page
* removed policyId from route path
* fix type issue
* updated the select agent field layout per the designs
* simpified item rendering in combobox and fixed combobox z-index issue
* added comment
* fix types and i18n
* updated icon and removed unused i18n
* refactor to using styled components for cusomt z-index styling
* attempt to fix integration test
* added scroll functionality for dealing with fixed footers that might be obstructing content
* fix scroll direction!
* attempting another scroll algorithm
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../scroll_into_view_if_necessary.js | 9 +-
.../web_element_wrapper.ts | 15 +-
.../create_package_policy_page/index.tsx | 73 ++++---
.../step_select_agent_policy.tsx | 190 ++++++++---------
.../step_select_package.tsx | 198 ------------------
.../package_policies_table.tsx | 15 +-
.../public/applications/integrations/app.tsx | 9 +-
.../applications/integrations/hooks/index.ts | 1 +
.../hooks/use_agent_policy_context.tsx | 36 ++++
.../sections/epm/screens/detail/index.tsx | 66 ++++--
.../fleet/public/constants/page_paths.ts | 5 +-
.../public/types/intra_app_route_state.ts | 5 +
.../translations/translations/ja-JP.json | 5 -
.../translations/translations/zh-CN.json | 6 -
.../synthetics_integration_page.ts | 4 +
15 files changed, 265 insertions(+), 372 deletions(-)
delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx
create mode 100644 x-pack/plugins/fleet/public/applications/integrations/hooks/use_agent_policy_context.tsx
diff --git a/test/functional/services/lib/web_element_wrapper/scroll_into_view_if_necessary.js b/test/functional/services/lib/web_element_wrapper/scroll_into_view_if_necessary.js
index 88a6ad0e3ab15..514d1bb1d9d7b 100644
--- a/test/functional/services/lib/web_element_wrapper/scroll_into_view_if_necessary.js
+++ b/test/functional/services/lib/web_element_wrapper/scroll_into_view_if_necessary.js
@@ -27,7 +27,7 @@
* SOFTWARE.
*/
-export function scrollIntoViewIfNecessary(target, fixedHeaderHeight) {
+export function scrollIntoViewIfNecessary(target, fixedHeaderHeight, fixedFooterHeight) {
var rootScroller = document.scrollingElement || document.documentElement;
if (!rootScroller) {
throw new Error('Unable to find document.scrollingElement or document.documentElement');
@@ -63,4 +63,11 @@ export function scrollIntoViewIfNecessary(target, fixedHeaderHeight) {
if (additionalScrollNecessary > 0) {
rootScroller.scrollTop = rootScroller.scrollTop - additionalScrollNecessary;
}
+
+ if (fixedFooterHeight) {
+ var bottomOfVisibility = viewportHeight - fixedFooterHeight;
+ if (bottomOfVisibility < boundingRect.bottom) {
+ rootScroller.scrollTop = rootScroller.scrollTop + fixedFooterHeight;
+ }
+ }
}
diff --git a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts
index 148c21ffac191..4b164402bfb70 100644
--- a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts
+++ b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts
@@ -692,11 +692,22 @@ export class WebElementWrapper {
* @nonstandard
* @return {Promise}
*/
- public async scrollIntoViewIfNecessary(topOffset?: number): Promise {
+ public async scrollIntoViewIfNecessary(
+ topOffsetOrOptions?: number | { topOffset?: number; bottomOffset?: number }
+ ): Promise {
+ let topOffset: undefined | number;
+ let bottomOffset: undefined | number;
+ if (typeof topOffsetOrOptions === 'number') {
+ topOffset = topOffsetOrOptions;
+ } else {
+ topOffset = topOffsetOrOptions?.topOffset;
+ bottomOffset = topOffsetOrOptions?.bottomOffset;
+ }
await this.driver.executeScript(
scrollIntoViewIfNecessary,
this._webElement,
- topOffset || this.fixedHeaderHeight
+ topOffset || this.fixedHeaderHeight,
+ bottomOffset
);
}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx
index 3e4b120a28f8e..75fc06c1a4494 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx
@@ -7,7 +7,7 @@
import type { ReactEventHandler } from 'react';
import React, { useState, useEffect, useMemo, useCallback } from 'react';
-import { useRouteMatch, useHistory } from 'react-router-dom';
+import { useRouteMatch, useHistory, useLocation } from 'react-router-dom';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -49,7 +49,6 @@ import { CreatePackagePolicyPageLayout } from './components';
import type { CreatePackagePolicyFrom, PackagePolicyFormState } from './types';
import type { PackagePolicyValidationResults } from './services';
import { validatePackagePolicy, validationHasErrors } from './services';
-import { StepSelectPackage } from './step_select_package';
import { StepSelectAgentPolicy } from './step_select_agent_policy';
import { StepConfigurePackagePolicy } from './step_configure_package';
import { StepDefinePackagePolicy } from './step_define_package_policy';
@@ -60,9 +59,15 @@ const StepsWithLessPadding = styled(EuiSteps)`
}
`;
+const CustomEuiBottomBar = styled(EuiBottomBar)`
+ // Set a relatively _low_ z-index value here to account for EuiComboBox popover that might appear under the bottom bar
+ z-index: 50;
+`;
+
interface AddToPolicyParams {
pkgkey: string;
integration?: string;
+ policyId?: string;
}
interface AddFromPolicyParams {
@@ -81,10 +86,14 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
const routeState = useIntraAppState();
const from: CreatePackagePolicyFrom = 'policyId' in params ? 'policy' : 'package';
+ const { search } = useLocation();
+ const queryParams = useMemo(() => new URLSearchParams(search), [search]);
+ const policyId = useMemo(() => queryParams.get('policyId') ?? undefined, [queryParams]);
+
// Agent policy and package info states
- const [agentPolicy, setAgentPolicy] = useState();
+ const [agentPolicy, setAgentPolicy] = useState();
const [packageInfo, setPackageInfo] = useState();
- const [isLoadingSecondStep, setIsLoadingSecondStep] = useState(false);
+ const [isLoadingAgentPolicyStep, setIsLoadingAgentPolicyStep] = useState(false);
// Retrieve agent count
const agentPolicyId = agentPolicy?.id;
@@ -286,6 +295,13 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
agentPolicyName: agentPolicy.name,
},
})
+ : (params as AddToPolicyParams)?.policyId && agentPolicy && agentCount === 0
+ ? i18n.translate('xpack.fleet.createPackagePolicy.addAgentNextNotification', {
+ defaultMessage: `The policy has been updated. Add an agent to the '{agentPolicyName}' policy to deploy this policy.`,
+ values: {
+ agentPolicyName: agentPolicy.name,
+ },
+ })
: undefined,
'data-test-subj': 'packagePolicyCreateSuccessToast',
});
@@ -337,32 +353,20 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
),
- [params, updatePackageInfo, agentPolicy, updateAgentPolicy]
+ [params, updatePackageInfo, agentPolicy, updateAgentPolicy, policyId]
);
const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create');
- const stepSelectPackage = useMemo(
- () => (
-
- ),
- [params, updateAgentPolicy, packageInfo, updatePackageInfo]
- );
-
const stepConfigurePackagePolicy = useMemo(
() =>
- isLoadingSecondStep ? (
+ isLoadingAgentPolicyStep ? (
) : agentPolicy && packageInfo ? (
<>
@@ -399,7 +403,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
),
[
- isLoadingSecondStep,
+ isLoadingAgentPolicyStep,
agentPolicy,
packageInfo,
packagePolicy,
@@ -413,27 +417,20 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
);
const steps: EuiStepProps[] = [
- from === 'package'
- ? {
- title: i18n.translate('xpack.fleet.createPackagePolicy.stepSelectAgentPolicyTitle', {
- defaultMessage: 'Select an agent policy',
- }),
- children: stepSelectAgentPolicy,
- }
- : {
- title: i18n.translate('xpack.fleet.createPackagePolicy.stepSelectPackageTitle', {
- defaultMessage: 'Select an integration',
- }),
- children: stepSelectPackage,
- },
{
title: i18n.translate('xpack.fleet.createPackagePolicy.stepConfigurePackagePolicyTitle', {
defaultMessage: 'Configure integration',
}),
- status: !packageInfo || !agentPolicy || isLoadingSecondStep ? 'disabled' : undefined,
+ status: !packageInfo || !agentPolicy || isLoadingAgentPolicyStep ? 'disabled' : undefined,
'data-test-subj': 'dataCollectionSetupStep',
children: stepConfigurePackagePolicy,
},
+ {
+ title: i18n.translate('xpack.fleet.createPackagePolicy.stepSelectAgentPolicyTitle', {
+ defaultMessage: 'Apply to agent policy',
+ }),
+ children: stepSelectAgentPolicy,
+ },
];
return (
@@ -459,10 +456,10 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
)}
-
+
- {!isLoadingSecondStep && agentPolicy && packageInfo && formState === 'INVALID' ? (
+ {!isLoadingAgentPolicyStep && agentPolicy && packageInfo && formState === 'INVALID' ? (
{
-
+
);
};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx
index 26d47cbff5b86..e2f5a7249ff0a 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx
@@ -14,9 +14,11 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiComboBox,
- EuiTextColor,
EuiPortal,
EuiFormRow,
+ EuiDescribedFormGroup,
+ EuiTitle,
+ EuiText,
EuiLink,
} from '@elastic/eui';
@@ -32,38 +34,32 @@ import {
} from '../../../hooks';
import { CreateAgentPolicyFlyout } from '../list_page/components';
-const AgentPolicyWrapper = styled(EuiFormRow)`
+const AgentPolicyFormRow = styled(EuiFormRow)`
.euiFormRow__label {
width: 100%;
}
`;
-// Custom styling for drop down list items due to:
-// 1) the max-width and overflow properties is added to prevent long agent policy
-// names/descriptions from overflowing the flex items
-// 2) max-width is built from the grow property on the flex items because the value
-// changes based on if Fleet is enabled/setup or not
-const AgentPolicyNameColumn = styled(EuiFlexItem)`
- max-width: ${(props) => `${((props.grow as number) / 9) * 100}%`};
- overflow: hidden;
-`;
-const AgentPolicyDescriptionColumn = styled(EuiFlexItem)`
- max-width: ${(props) => `${((props.grow as number) / 9) * 100}%`};
- overflow: hidden;
-`;
-
export const StepSelectAgentPolicy: React.FunctionComponent<{
pkgkey: string;
updatePackageInfo: (packageInfo: PackageInfo | undefined) => void;
+ defaultAgentPolicyId?: string;
agentPolicy: AgentPolicy | undefined;
updateAgentPolicy: (agentPolicy: AgentPolicy | undefined) => void;
setIsLoadingSecondStep: (isLoading: boolean) => void;
-}> = ({ pkgkey, updatePackageInfo, agentPolicy, updateAgentPolicy, setIsLoadingSecondStep }) => {
+}> = ({
+ pkgkey,
+ updatePackageInfo,
+ agentPolicy,
+ updateAgentPolicy,
+ setIsLoadingSecondStep,
+ defaultAgentPolicyId,
+}) => {
const { isReady: isFleetReady } = useFleetStatus();
// Selected agent policy state
const [selectedPolicyId, setSelectedPolicyId] = useState(
- agentPolicy ? agentPolicy.id : undefined
+ agentPolicy?.id ?? defaultAgentPolicyId
);
const [selectedAgentPolicyError, setSelectedAgentPolicyError] = useState();
@@ -223,95 +219,91 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{
) : null}
-
-
+
+
-
-
-
- setIsCreateAgentPolicyFlyoutOpen(true)}
- >
-
-
-
-
-
+
+
}
- helpText={
- isFleetReady && selectedPolicyId ? (
-
- ) : null
+ description={
+
+
+
+
+
}
>
- ) => {
- return (
-
-
- {option.label}
-
-
-
- {agentPoliciesById[option.value!].description}
-
-
- {isFleetReady ? (
-
-
-
-
-
- ) : null}
-
- );
- }}
- selectedOptions={selectedAgentPolicyOption ? [selectedAgentPolicyOption] : []}
- onChange={(options) => {
- const selectedOption = options[0] || undefined;
- if (selectedOption) {
- if (selectedOption.value !== selectedPolicyId) {
- setSelectedPolicyId(selectedOption.value);
+ label={
+
+
+
+
+
+
+ setIsCreateAgentPolicyFlyoutOpen(true)}
+ >
+
+
+
+
+
+ }
+ helpText={
+ isFleetReady && selectedPolicyId ? (
+
+ ) : null
+ }
+ >
+
-
+ )}
+ singleSelection={{ asPlainText: true }}
+ isClearable={false}
+ fullWidth={true}
+ isLoading={isAgentPoliciesLoading || isPackageInfoLoading}
+ options={agentPolicyOptions}
+ selectedOptions={selectedAgentPolicyOption ? [selectedAgentPolicyOption] : []}
+ onChange={(options) => {
+ const selectedOption = options[0] || undefined;
+ if (selectedOption) {
+ if (selectedOption.value !== selectedPolicyId) {
+ setSelectedPolicyId(selectedOption.value);
+ }
+ } else {
+ setSelectedPolicyId(undefined);
+ }
+ }}
+ />
+
+
{/* Display selected agent policy error if there is one */}
{selectedAgentPolicyError ? (
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx
deleted file mode 100644
index 50c63274b5e85..0000000000000
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx
+++ /dev/null
@@ -1,198 +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 React, { useEffect, useState, Fragment } from 'react';
-import { i18n } from '@kbn/i18n';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiFlexGroup, EuiFlexItem, EuiSelectable, EuiSpacer } from '@elastic/eui';
-
-import { Error, PackageIcon } from '../../../components';
-import type { AgentPolicy, PackageInfo, PackagePolicy, GetPackagesResponse } from '../../../types';
-import {
- useGetOneAgentPolicy,
- useGetPackages,
- useGetLimitedPackages,
- sendGetPackageInfoByKey,
-} from '../../../hooks';
-import { pkgKeyFromPackageInfo } from '../../../services';
-
-export const StepSelectPackage: React.FunctionComponent<{
- agentPolicyId: string;
- updateAgentPolicy: (agentPolicy: AgentPolicy | undefined) => void;
- packageInfo?: PackageInfo;
- updatePackageInfo: (packageInfo: PackageInfo | undefined) => void;
- setIsLoadingSecondStep: (isLoading: boolean) => void;
-}> = ({
- agentPolicyId,
- updateAgentPolicy,
- packageInfo,
- updatePackageInfo,
- setIsLoadingSecondStep,
-}) => {
- // Selected package state
- const [selectedPkgKey, setSelectedPkgKey] = useState(
- packageInfo ? pkgKeyFromPackageInfo(packageInfo) : undefined
- );
- const [selectedPkgError, setSelectedPkgError] = useState();
-
- // Fetch agent policy info
- const {
- data: agentPolicyData,
- error: agentPolicyError,
- isLoading: isAgentPoliciesLoading,
- } = useGetOneAgentPolicy(agentPolicyId);
-
- // Fetch packages info
- // Filter out limited packages already part of selected agent policy
- const [packages, setPackages] = useState([]);
- const {
- data: packagesData,
- error: packagesError,
- isLoading: isPackagesLoading,
- } = useGetPackages();
- const {
- data: limitedPackagesData,
- isLoading: isLimitedPackagesLoading,
- } = useGetLimitedPackages();
- useEffect(() => {
- if (packagesData?.response && limitedPackagesData?.response && agentPolicyData?.item) {
- const allPackages = packagesData.response;
- const limitedPackages = limitedPackagesData.response;
- const usedLimitedPackages = (agentPolicyData.item.package_policies as PackagePolicy[])
- .map((packagePolicy) => packagePolicy.package?.name || '')
- .filter((pkgName) => limitedPackages.includes(pkgName));
- setPackages(allPackages.filter((pkg) => !usedLimitedPackages.includes(pkg.name)));
- }
- }, [packagesData, limitedPackagesData, agentPolicyData]);
-
- // Update parent agent policy state
- useEffect(() => {
- if (agentPolicyData && agentPolicyData.item) {
- updateAgentPolicy(agentPolicyData.item);
- }
- }, [agentPolicyData, updateAgentPolicy]);
-
- // Update parent selected package state
- useEffect(() => {
- const fetchPackageInfo = async () => {
- if (selectedPkgKey) {
- setIsLoadingSecondStep(true);
- const { data, error } = await sendGetPackageInfoByKey(selectedPkgKey);
- if (error) {
- setSelectedPkgError(error);
- updatePackageInfo(undefined);
- } else if (data && data.response) {
- setSelectedPkgError(undefined);
- updatePackageInfo(data.response);
- }
- setIsLoadingSecondStep(false);
- } else {
- setSelectedPkgError(undefined);
- updatePackageInfo(undefined);
- }
- };
- if (!packageInfo || selectedPkgKey !== pkgKeyFromPackageInfo(packageInfo)) {
- fetchPackageInfo();
- }
- }, [selectedPkgKey, packageInfo, updatePackageInfo, setIsLoadingSecondStep]);
-
- // Display agent policy error if there is one
- if (agentPolicyError) {
- return (
-
- }
- error={agentPolicyError}
- />
- );
- }
-
- // Display packages list error if there is one
- if (packagesError) {
- return (
-
- }
- error={packagesError}
- />
- );
- }
-
- return (
-
-
- {
- const pkgkey = `${name}-${version}`;
- return {
- label: title || name,
- key: pkgkey,
- prepend: ,
- checked: selectedPkgKey === pkgkey ? 'on' : undefined,
- };
- })}
- listProps={{
- bordered: true,
- }}
- searchProps={{
- placeholder: i18n.translate(
- 'xpack.fleet.createPackagePolicy.stepSelectPackage.filterPackagesInputPlaceholder',
- {
- defaultMessage: 'Search for integrations',
- }
- ),
- }}
- height={240}
- onChange={(options) => {
- const selectedOption = options.find((option) => option.checked === 'on');
- if (selectedOption) {
- if (selectedOption.key !== selectedPkgKey) {
- setSelectedPkgKey(selectedOption.key);
- }
- } else {
- setSelectedPkgKey(undefined);
- }
- }}
- >
- {(list, search) => (
-
- {search}
-
- {list}
-
- )}
-
-
- {/* Display selected package error if there is one */}
- {selectedPkgError ? (
-
-
- }
- error={selectedPkgError}
- />
-
- ) : null}
-
- );
-};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx
index 2e6e7fb984ef0..49af14b7234fa 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx
@@ -18,9 +18,11 @@ import {
EuiText,
} from '@elastic/eui';
+import { INTEGRATIONS_PLUGIN_ID } from '../../../../../../../../common';
+import { pagePathGetters } from '../../../../../../../constants';
import type { AgentPolicy, PackagePolicy } from '../../../../../types';
import { PackageIcon, PackagePolicyActionsMenu } from '../../../../../components';
-import { useCapabilities, useLink } from '../../../../../hooks';
+import { useCapabilities, useStartServices } from '../../../../../hooks';
interface InMemoryPackagePolicy extends PackagePolicy {
packageName?: string;
@@ -49,7 +51,7 @@ export const PackagePoliciesTable: React.FunctionComponent = ({
agentPolicy,
...rest
}) => {
- const { getHref } = useLink();
+ const { application } = useStartServices();
const hasWriteCapabilities = useCapabilities().write;
// With the package policies provided on input, generate the list of package policies
@@ -194,8 +196,13 @@ export const PackagePoliciesTable: React.FunctionComponent = ({
{
+ application.navigateToApp(INTEGRATIONS_PLUGIN_ID, {
+ path: `#${pagePathGetters.integrations_all()[1]}`,
+ state: { forAgentPolicyId: agentPolicy.id },
+ });
+ }}
>
-
- {children}
-
+
+
+ {children}
+
+
diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/index.ts b/x-pack/plugins/fleet/public/applications/integrations/hooks/index.ts
index 7f758e3c472c1..9e4cdde064e70 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/hooks/index.ts
+++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/index.ts
@@ -10,3 +10,4 @@ export { useBreadcrumbs } from './use_breadcrumbs';
export { useLinks } from './use_links';
export * from './use_local_search';
export * from './use_package_install';
+export * from './use_agent_policy_context';
diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_agent_policy_context.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_agent_policy_context.tsx
new file mode 100644
index 0000000000000..859db79ad159b
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_agent_policy_context.tsx
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { FunctionComponent } from 'react';
+import React, { createContext, useContext, useRef, useCallback } from 'react';
+
+import type { IntegrationsAppBrowseRouteState } from '../../../types';
+import { useIntraAppState } from '../../../hooks';
+
+interface AgentPolicyContextValue {
+ getId(): string | undefined;
+}
+
+const AgentPolicyContext = createContext({ getId: () => undefined });
+
+export const AgentPolicyContextProvider: FunctionComponent = ({ children }) => {
+ const maybeState = useIntraAppState();
+ const ref = useRef(maybeState?.forAgentPolicyId);
+
+ const getId = useCallback(() => {
+ return ref.current;
+ }, []);
+ return {children} ;
+};
+
+export const useAgentPolicyContext = () => {
+ const ctx = useContext(AgentPolicyContext);
+ if (!ctx) {
+ throw new Error('useAgentPolicyContext can only be used inside of AgentPolicyContextProvider');
+ }
+ return ctx;
+};
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
index 68736455b818f..99a29a8194f9b 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
@@ -37,7 +37,12 @@ import {
INTEGRATIONS_ROUTING_PATHS,
pagePathGetters,
} from '../../../../constants';
-import { useCapabilities, useGetPackageInfoByKey, useLink } from '../../../../hooks';
+import {
+ useCapabilities,
+ useGetPackageInfoByKey,
+ useLink,
+ useAgentPolicyContext,
+} from '../../../../hooks';
import { pkgKeyFromPackageInfo } from '../../../../services';
import type {
CreatePackagePolicyRouteState,
@@ -79,6 +84,7 @@ function Breadcrumbs({ packageTitle }: { packageTitle: string }) {
}
export function Detail() {
+ const { getId: getAgentPolicyId } = useAgentPolicyContext();
const { pkgkey, panel } = useParams();
const { getHref } = useLink();
const hasWriteCapabilites = useCapabilities().write;
@@ -87,6 +93,7 @@ export function Detail() {
const queryParams = useMemo(() => new URLSearchParams(search), [search]);
const integration = useMemo(() => queryParams.get('integration'), [queryParams]);
const services = useStartServices();
+ const agentPolicyIdFromContext = getAgentPolicyId();
// Package info state
const [packageInfo, setPackageInfo] = useState(null);
@@ -211,24 +218,42 @@ export function Detail() {
search,
hash,
});
- const redirectToPath: CreatePackagePolicyRouteState['onSaveNavigateTo'] &
- CreatePackagePolicyRouteState['onCancelNavigateTo'] = [
- INTEGRATIONS_PLUGIN_ID,
- {
- path: currentPath,
- },
- ];
- const redirectBackRouteState: CreatePackagePolicyRouteState = {
- onSaveNavigateTo: redirectToPath,
- onCancelNavigateTo: redirectToPath,
- onCancelUrl: currentPath,
- };
const path = pagePathGetters.add_integration_to_policy({
pkgkey,
...(integration ? { integration } : {}),
+ ...(agentPolicyIdFromContext ? { agentPolicyId: agentPolicyIdFromContext } : {}),
})[1];
+ let redirectToPath: CreatePackagePolicyRouteState['onSaveNavigateTo'] &
+ CreatePackagePolicyRouteState['onCancelNavigateTo'];
+
+ if (agentPolicyIdFromContext) {
+ redirectToPath = [
+ PLUGIN_ID,
+ {
+ path: `#${
+ pagePathGetters.policy_details({
+ policyId: agentPolicyIdFromContext,
+ })[1]
+ }`,
+ },
+ ];
+ } else {
+ redirectToPath = [
+ INTEGRATIONS_PLUGIN_ID,
+ {
+ path: currentPath,
+ },
+ ];
+ }
+
+ const redirectBackRouteState: CreatePackagePolicyRouteState = {
+ onSaveNavigateTo: redirectToPath,
+ onCancelNavigateTo: redirectToPath,
+ onCancelUrl: currentPath,
+ };
+
services.application.navigateToApp(PLUGIN_ID, {
// Necessary because of Fleet's HashRouter. Can be changed when
// https://github.com/elastic/kibana/issues/96134 is resolved
@@ -236,7 +261,16 @@ export function Detail() {
state: redirectBackRouteState,
});
},
- [history, hash, pathname, search, pkgkey, integration, services.application]
+ [
+ history,
+ hash,
+ pathname,
+ search,
+ pkgkey,
+ integration,
+ services.application,
+ agentPolicyIdFromContext,
+ ]
);
const headerRightContent = useMemo(
@@ -284,6 +318,9 @@ export function Detail() {
href={getHref('add_integration_to_policy', {
pkgkey,
...(integration ? { integration } : {}),
+ ...(agentPolicyIdFromContext
+ ? { agentPolicyId: agentPolicyIdFromContext }
+ : {}),
})}
onClick={handleAddIntegrationPolicyClick}
data-test-subj="addIntegrationPolicyButton"
@@ -325,6 +362,7 @@ export function Detail() {
packageInstallStatus,
pkgkey,
updateAvailable,
+ agentPolicyIdFromContext,
]
);
diff --git a/x-pack/plugins/fleet/public/constants/page_paths.ts b/x-pack/plugins/fleet/public/constants/page_paths.ts
index 4ff71cf162e22..3c9c0e5759615 100644
--- a/x-pack/plugins/fleet/public/constants/page_paths.ts
+++ b/x-pack/plugins/fleet/public/constants/page_paths.ts
@@ -111,9 +111,10 @@ export const pagePathGetters: {
FLEET_BASE_PATH,
`/policies/${policyId}/add-integration`,
],
- add_integration_to_policy: ({ pkgkey, integration }) => [
+ add_integration_to_policy: ({ pkgkey, integration, agentPolicyId }) => [
FLEET_BASE_PATH,
- `/integrations/${pkgkey}/add-integration${integration ? `/${integration}` : ''}`,
+ // prettier-ignore
+ `/integrations/${pkgkey}/add-integration${integration ? `/${integration}` : ''}${agentPolicyId ? `?policyId=${agentPolicyId}` : ''}`,
],
edit_integration: ({ policyId, packagePolicyId }) => [
FLEET_BASE_PATH,
diff --git a/x-pack/plugins/fleet/public/types/intra_app_route_state.ts b/x-pack/plugins/fleet/public/types/intra_app_route_state.ts
index 74921daa0d2a1..36fd32c2a6584 100644
--- a/x-pack/plugins/fleet/public/types/intra_app_route_state.ts
+++ b/x-pack/plugins/fleet/public/types/intra_app_route_state.ts
@@ -39,6 +39,11 @@ export interface AgentDetailsReassignPolicyAction {
onDoneNavigateTo?: Parameters;
}
+export interface IntegrationsAppBrowseRouteState {
+ /** The agent policy that we are browsing integrations for */
+ forAgentPolicyId: string;
+}
+
/**
* All possible Route states.
*/
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 9be5957cd4718..74a0154c3242b 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -8996,11 +8996,6 @@
"xpack.fleet.createPackagePolicy.stepConfigure.toggleAdvancedOptionsButtonText": "高度なオプション",
"xpack.fleet.createPackagePolicy.stepConfigurePackagePolicyTitle": "統合の構成",
"xpack.fleet.createPackagePolicy.stepSelectAgentPolicyTitle": "エージェントポリシーを選択",
- "xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingPackagesTitle": "統合の読み込みエラー",
- "xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingPolicyTitle": "エージェントポリシー情報の読み込みエラー",
- "xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingSelectedPackageTitle": "選択した統合の読み込みエラー",
- "xpack.fleet.createPackagePolicy.stepSelectPackage.filterPackagesInputPlaceholder": "統合を検索",
- "xpack.fleet.createPackagePolicy.stepSelectPackageTitle": "統合を選択",
"xpack.fleet.createPackagePolicy.StepSelectPolicy.addButton": "エージェントポリシーを作成",
"xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyLabel": "エージェントポリシー",
"xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyPlaceholderText": "この統合を追加するエージェントポリシーを選択",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index d6ebf8a32bcc4..aebee851043b6 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -9077,13 +9077,7 @@
"xpack.fleet.createPackagePolicy.stepConfigure.toggleAdvancedOptionsButtonText": "高级选项",
"xpack.fleet.createPackagePolicy.stepConfigurePackagePolicyTitle": "配置集成",
"xpack.fleet.createPackagePolicy.stepSelectAgentPolicyTitle": "选择代理策略",
- "xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingPackagesTitle": "加载集成时出错",
- "xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingPolicyTitle": "加载代理策略信息时出错",
- "xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingSelectedPackageTitle": "加载选定集成时出错",
- "xpack.fleet.createPackagePolicy.stepSelectPackage.filterPackagesInputPlaceholder": "搜索集成",
- "xpack.fleet.createPackagePolicy.stepSelectPackageTitle": "选择集成",
"xpack.fleet.createPackagePolicy.StepSelectPolicy.addButton": "创建代理策略",
- "xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyAgentsCountText": "{count, plural, other {# 个代理}}已注册",
"xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyAgentsDescriptionText": "{count, plural, other {# 个代理}}已注册到选定代理策略。",
"xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyLabel": "代理策略",
"xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyPlaceholderText": "选择要将此集成添加到的代理策略",
diff --git a/x-pack/test/functional/page_objects/synthetics_integration_page.ts b/x-pack/test/functional/page_objects/synthetics_integration_page.ts
index 69ae3f43d26f2..56a67d9e6fbd4 100644
--- a/x-pack/test/functional/page_objects/synthetics_integration_page.ts
+++ b/x-pack/test/functional/page_objects/synthetics_integration_page.ts
@@ -16,6 +16,8 @@ export function SyntheticsIntegrationPageProvider({
const testSubjects = getService('testSubjects');
const comboBox = getService('comboBox');
+ const fixedFooterHeight = 72; // Size of EuiBottomBar more or less
+
return {
/**
* Navigates to the Synthetics Integration page
@@ -85,6 +87,7 @@ export function SyntheticsIntegrationPageProvider({
*/
async fillTextInputByTestSubj(testSubj: string, value: string) {
const field = await testSubjects.find(testSubj, 5000);
+ await field.scrollIntoViewIfNecessary({ bottomOffset: fixedFooterHeight });
await field.click();
await field.clearValue();
await field.type(value);
@@ -96,6 +99,7 @@ export function SyntheticsIntegrationPageProvider({
* @params {value} the value of the input
*/
async fillTextInput(field: WebElementWrapper, value: string) {
+ await field.scrollIntoViewIfNecessary({ bottomOffset: fixedFooterHeight });
await field.click();
await field.clearValue();
await field.type(value);
From 0bb3b2c6661fc99d8e92e0c4b8ecb655464b872c Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Tue, 15 Jun 2021 09:28:51 +0200
Subject: [PATCH 28/91] rename beta badge (#101884)
---
.../editor_frame/workspace_panel/chart_switch.tsx | 8 ++++----
.../lens/public/heatmap_visualization/visualization.tsx | 2 +-
x-pack/plugins/lens/public/types.ts | 7 +++++--
3 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx
index ba0e09bdd894c..0c3a992e3dd7a 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx
@@ -297,7 +297,7 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) {
),
append:
- v.selection.dataLoss !== 'nothing' || v.showBetaBadge ? (
+ v.selection.dataLoss !== 'nothing' || v.showExperimentalBadge ? (
) : null}
- {v.showBetaBadge ? (
+ {v.showExperimentalBadge ? (
diff --git a/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx b/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx
index 54f9c70824831..fce5bf30f47ed 100644
--- a/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx
+++ b/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx
@@ -88,7 +88,7 @@ export const getHeatmapVisualization = ({
defaultMessage: 'Heatmap',
}),
groupLabel: groupLabelForBar,
- showBetaBadge: true,
+ showExperimentalBadge: true,
},
],
diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts
index b421d57dae6e1..7baba15f0fac6 100644
--- a/x-pack/plugins/lens/public/types.ts
+++ b/x-pack/plugins/lens/public/types.ts
@@ -49,6 +49,7 @@ export interface EditorFrameProps {
initialContext?: VisualizeFieldContext;
showNoDataPopover: () => void;
}
+
export interface EditorFrameInstance {
EditorFrameContainer: (props: EditorFrameProps) => React.ReactElement;
}
@@ -570,9 +571,9 @@ export interface VisualizationType {
*/
sortPriority?: number;
/**
- * Indicates if visualization is in the beta stage.
+ * Indicates if visualization is in the experimental stage.
*/
- showBetaBadge?: boolean;
+ showExperimentalBadge?: boolean;
}
export interface Visualization {
@@ -734,6 +735,7 @@ interface LensEditContextMapping {
[LENS_EDIT_RESIZE_ACTION]: LensResizeActionData;
[LENS_TOGGLE_ACTION]: LensToggleActionData;
}
+
type LensEditSupportedActions = keyof LensEditContextMapping;
export type LensEditPayload = {
@@ -746,6 +748,7 @@ export interface LensEditEvent {
name: 'edit';
data: EditPayloadContext;
}
+
export interface LensTableRowContextMenuEvent {
name: 'tableRowContextMenuClick';
data: RowClickContext['data'];
From a8836af1c489331b7123aeee71c78a80d7b86d07 Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Tue, 15 Jun 2021 09:33:29 +0200
Subject: [PATCH 29/91] [Lens] do not allow custom bounds on percentage chart
(#101743)
---
.../axis_settings_popover.test.tsx | 1 +
.../xy_visualization/axis_settings_popover.tsx | 11 +++++++++--
.../public/xy_visualization/xy_config_panel.tsx | 16 ++++++++++++++++
3 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.test.tsx b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.test.tsx
index 047c95846cd27..2e5fdf8493e24 100644
--- a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.test.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.test.tsx
@@ -33,6 +33,7 @@ describe('Axes Settings', () => {
toggleTickLabelsVisibility: jest.fn(),
toggleGridlinesVisibility: jest.fn(),
hasBarOrAreaOnAxis: false,
+ hasPercentageAxis: false,
};
});
diff --git a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx
index 43ebc91f533a4..a0d1dae2145d5 100644
--- a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx
@@ -31,6 +31,7 @@ import { ToolbarButtonProps } from '../../../../../src/plugins/kibana_react/publ
import { validateExtent } from './axes_configuration';
type AxesSettingsConfigKeys = keyof AxesSettingsConfig;
+
export interface AxisSettingsPopoverProps {
/**
* Determines the axis
@@ -93,8 +94,10 @@ export interface AxisSettingsPopoverProps {
*/
setExtent?: (extent: AxisExtentConfig | undefined) => void;
hasBarOrAreaOnAxis: boolean;
+ hasPercentageAxis: boolean;
dataBounds?: { min: number; max: number };
}
+
const popoverConfig = (
axis: AxesSettingsConfigKeys,
isHorizontal: boolean
@@ -168,6 +171,7 @@ export const AxisSettingsPopover: React.FunctionComponent {
const isHorizontal = layers?.length ? isHorizontalChart(layers) : false;
@@ -333,10 +337,13 @@ export const AxisSettingsPopover: React.FunctionComponent {
const newMode = id.replace(idPrefix, '') as AxisExtentConfig['mode'];
@@ -350,7 +357,7 @@ export const AxisSettingsPopover: React.FunctionComponent
- {localExtent.mode === 'custom' && (
+ {localExtent.mode === 'custom' && !hasPercentageAxis && (
<>
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
index b3d1f8f062b73..04533f6c914e1 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
@@ -36,6 +36,7 @@ import {
YAxisMode,
AxesSettingsConfig,
AxisExtentConfig,
+ XYState,
} from './types';
import { isHorizontalChart, isHorizontalSeries, getSeriesColor } from './state_helpers';
import { trackUiEvent } from '../lens_ui_telemetry';
@@ -162,6 +163,18 @@ const getDataBounds = function (
return groups;
};
+function hasPercentageAxis(axisGroups: GroupsConfiguration, groupId: string, state: XYState) {
+ return Boolean(
+ axisGroups
+ .find((group) => group.groupId === groupId)
+ ?.series.some(({ layer: layerId }) =>
+ state?.layers.find(
+ (layer) => layer.layerId === layerId && layer.seriesType.includes('percentage')
+ )
+ )
+ );
+}
+
export const XyToolbar = memo(function XyToolbar(props: VisualizationToolbarProps) {
const { state, setState, frame } = props;
@@ -377,6 +390,7 @@ export const XyToolbar = memo(function XyToolbar(props: VisualizationToolbarProp
setExtent={setLeftExtent}
hasBarOrAreaOnAxis={hasBarOrAreaOnLeftAxis}
dataBounds={dataBounds.left}
+ hasPercentageAxis={hasPercentageAxis(axisGroups, 'left', state)}
/>
group.groupId === 'right') || {}).length ===
0
}
+ hasPercentageAxis={hasPercentageAxis(axisGroups, 'right', state)}
isAxisTitleVisible={axisTitlesVisibilitySettings.yRight}
toggleAxisTitleVisibility={onAxisTitlesVisibilitySettingsChange}
extent={state?.yRightExtent || { mode: 'full' }}
From e418a895c30af3596e6d534643c6d4c41ce32ec8 Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Tue, 15 Jun 2021 09:44:43 +0200
Subject: [PATCH 30/91] reduce inner timeout (#102081)
---
x-pack/test/functional/page_objects/lens_page.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts
index b2790d0f7a08e..332a40795bee9 100644
--- a/x-pack/test/functional/page_objects/lens_page.ts
+++ b/x-pack/test/functional/page_objects/lens_page.ts
@@ -43,7 +43,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
},
async isLensPageOrFail() {
- return await testSubjects.existOrFail('lnsApp');
+ return await testSubjects.existOrFail('lnsApp', { timeout: 1000 });
},
/**
From 92b6535df26066515b5c83fddcb524eb23f547af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Casper=20H=C3=BCbertz?=
Date: Tue, 15 Jun 2021 10:12:45 +0200
Subject: [PATCH 31/91] [Observability] Fixes border shadow and other
enhancements (#102072)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* [Observability] Fixes border shadow and other enhancements
* Update x-pack/plugins/observability/public/components/app/section/index.tsx
Co-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com>
* [Observability] Remove unused import and formatting
Co-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../public/components/app/section/index.tsx | 57 ++++++++-----------
1 file changed, 25 insertions(+), 32 deletions(-)
diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx
index ddfd8ebed4f8f..191a1b2890ada 100644
--- a/x-pack/plugins/observability/public/components/app/section/index.tsx
+++ b/x-pack/plugins/observability/public/components/app/section/index.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiAccordion, EuiLink, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
+import { EuiAccordion, EuiPanel, EuiSpacer, EuiTitle, EuiButton } from '@elastic/eui';
import React from 'react';
import { ErrorPanel } from './error_panel';
import { usePluginContext } from '../../../hooks/use_plugin_context';
@@ -25,36 +25,29 @@ interface Props {
export function SectionContainer({ title, appLink, children, hasError }: Props) {
const { core } = usePluginContext();
return (
-
- {title}
-
- }
- extraAction={
- appLink?.href && (
-
- {appLink.label}
-
- )
- }
- >
- <>
-
-
- {hasError ? (
-
- ) : (
- <>
-
- {children}
- >
- )}
-
- >
-
+
+
+ {title}
+
+ }
+ extraAction={
+ appLink?.href && (
+ {appLink.label}
+ )
+ }
+ >
+ <>
+
+
+ {hasError ? : <>{children}>}
+
+ >
+
+
);
}
From 65fec6bd8e23513943bf699752f87dd8bf0ba4c9 Mon Sep 17 00:00:00 2001
From: Marco Liberati
Date: Tue, 15 Jun 2021 10:34:04 +0200
Subject: [PATCH 32/91] [Lens]Show dynamic coloring only for metrics (#101777)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../components/dimension_editor.test.tsx | 18 +++++++++++++++++-
.../components/dimension_editor.tsx | 7 ++++++-
2 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx
index 49003af28f3f1..3479a9e964d53 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx
+++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { EuiButtonGroup, EuiComboBox, EuiFieldText } from '@elastic/eui';
-import { FramePublicAPI, VisualizationDimensionEditorProps } from '../../types';
+import { FramePublicAPI, Operation, VisualizationDimensionEditorProps } from '../../types';
import { DatatableVisualizationState } from '../visualization';
import { createMockDatasource, createMockFramePublicAPI } from '../../editor_frame_service/mocks';
import { mountWithIntl } from '@kbn/test/jest';
@@ -213,6 +213,22 @@ describe('data table dimension editor', () => {
expect(instance.find(PalettePanelContainer).exists()).toBe(true);
});
+ it('should not show the dynamic coloring option for a bucketed operation', () => {
+ frame.activeData!.first.columns[0].meta.type = 'number';
+ frame.datasourceLayers.first.getOperationForColumnId = jest.fn(
+ () => ({ isBucketed: true } as Operation)
+ );
+ state.columns[0].colorMode = 'cell';
+ const instance = mountWithIntl( );
+
+ expect(instance.find('[data-test-subj="lnsDatatable_dynamicColoring_groups"]').exists()).toBe(
+ false
+ );
+ expect(instance.find('[data-test-subj="lnsDatatable_dynamicColoring_palette"]').exists()).toBe(
+ false
+ );
+ });
+
it('should show the summary field for non numeric columns', () => {
const instance = mountWithIntl( );
expect(instance.find('[data-test-subj="lnsDatatable_summaryrow_function"]').exists()).toBe(
diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx
index 6c39a04ae1504..cf15df07ec72c 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx
@@ -104,6 +104,11 @@ export function TableDimensionEditor(
currentData
);
+ const datasource = frame.datasourceLayers[state.layerId];
+ const showDynamicColoringFeature = Boolean(
+ isNumeric && !datasource?.getOperationForColumnId(accessor)?.isBucketed
+ );
+
const visibleColumnsCount = state.columns.filter((c) => !c.hidden).length;
const hasTransposedColumn = state.columns.some(({ isTransposed }) => isTransposed);
@@ -260,7 +265,7 @@ export function TableDimensionEditor(
)}
>
)}
- {isNumeric && (
+ {showDynamicColoringFeature && (
<>
Date: Tue, 15 Jun 2021 10:34:57 +0200
Subject: [PATCH 33/91] [Lens] Table headers are aligned the same as cells
(#101875)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../__snapshots__/table_basic.test.tsx.snap | 54 ++++++++++++++---
.../components/columns.tsx | 7 ++-
.../components/table_basic.tsx | 58 ++++++++++---------
3 files changed, 80 insertions(+), 39 deletions(-)
diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap b/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap
index a4be46f61990b..9ad39e1561e97 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap
+++ b/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap
@@ -126,7 +126,11 @@ exports[`DatatableComponent it renders actions column when there are row actions
},
},
"cellActions": undefined,
- "display": "a",
+ "display":
+ a
+
,
"displayAsText": "a",
"id": "a",
},
@@ -163,7 +167,11 @@ exports[`DatatableComponent it renders actions column when there are row actions
},
},
"cellActions": undefined,
- "display": "b",
+ "display":
+ b
+
,
"displayAsText": "b",
"id": "b",
},
@@ -200,7 +208,11 @@ exports[`DatatableComponent it renders actions column when there are row actions
},
},
"cellActions": undefined,
- "display": "c",
+ "display":
+ c
+
,
"displayAsText": "c",
"id": "c",
},
@@ -360,7 +372,11 @@ exports[`DatatableComponent it renders the title and value 1`] = `
},
},
"cellActions": undefined,
- "display": "a",
+ "display":
+ a
+
,
"displayAsText": "a",
"id": "a",
},
@@ -397,7 +413,11 @@ exports[`DatatableComponent it renders the title and value 1`] = `
},
},
"cellActions": undefined,
- "display": "b",
+ "display":
+ b
+
,
"displayAsText": "b",
"id": "b",
},
@@ -434,7 +454,11 @@ exports[`DatatableComponent it renders the title and value 1`] = `
},
},
"cellActions": undefined,
- "display": "c",
+ "display":
+ c
+
,
"displayAsText": "c",
"id": "c",
},
@@ -566,7 +590,11 @@ exports[`DatatableComponent it should not render actions on header when it is in
"showSortDesc": false,
},
"cellActions": undefined,
- "display": "a",
+ "display":
+ a
+
,
"displayAsText": "a",
"id": "a",
},
@@ -580,7 +608,11 @@ exports[`DatatableComponent it should not render actions on header when it is in
"showSortDesc": false,
},
"cellActions": undefined,
- "display": "b",
+ "display":
+ b
+
,
"displayAsText": "b",
"id": "b",
},
@@ -594,7 +626,11 @@ exports[`DatatableComponent it should not render actions on header when it is in
"showSortDesc": false,
},
"cellActions": undefined,
- "display": "c",
+ "display":
+ c
+
,
"displayAsText": "c",
"id": "c",
},
diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx
index 5c53d40f999b7..27c2db16dbd32 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx
+++ b/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx
@@ -35,7 +35,8 @@ export const createGridColumns = (
visibleColumns: string[],
formatFactory: FormatFactory,
onColumnResize: (eventData: { columnId: string; width: number | undefined }) => void,
- onColumnHide: (eventData: { columnId: string }) => void
+ onColumnHide: (eventData: { columnId: string }) => void,
+ alignments: Record
) => {
const columnsReverseLookup = table.columns.reduce<
Record
@@ -200,11 +201,13 @@ export const createGridColumns = (
});
}
}
+ const currentAlignment = alignments && alignments[field];
+ const alignmentClassName = `lnsTableCell--${currentAlignment}`;
const columnDefinition: EuiDataGridColumn = {
id: field,
cellActions,
- display: name,
+ display: {name}
,
displayAsText: name,
actions: {
showHide: false,
diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx
index cd990149fdaf5..2a6b4879854d7 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx
+++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx
@@ -180,34 +180,6 @@ export const DatatableComponent = (props: DatatableRenderProps) => {
[onEditAction, setColumnConfig, columnConfig]
);
- const columns: EuiDataGridColumn[] = useMemo(
- () =>
- createGridColumns(
- bucketColumns,
- firstLocalTable,
- handleFilterClick,
- handleTransposedColumnClick,
- isReadOnlySorted,
- columnConfig,
- visibleColumns,
- formatFactory,
- onColumnResize,
- onColumnHide
- ),
- [
- bucketColumns,
- firstLocalTable,
- handleFilterClick,
- handleTransposedColumnClick,
- isReadOnlySorted,
- columnConfig,
- visibleColumns,
- formatFactory,
- onColumnResize,
- onColumnHide,
- ]
- );
-
const isNumericMap: Record = useMemo(() => {
const numericMap: Record = {};
for (const column of firstLocalTable.columns) {
@@ -237,6 +209,36 @@ export const DatatableComponent = (props: DatatableRenderProps) => {
);
}, [firstTable, isNumericMap, columnConfig]);
+ const columns: EuiDataGridColumn[] = useMemo(
+ () =>
+ createGridColumns(
+ bucketColumns,
+ firstLocalTable,
+ handleFilterClick,
+ handleTransposedColumnClick,
+ isReadOnlySorted,
+ columnConfig,
+ visibleColumns,
+ formatFactory,
+ onColumnResize,
+ onColumnHide,
+ alignments
+ ),
+ [
+ bucketColumns,
+ firstLocalTable,
+ handleFilterClick,
+ handleTransposedColumnClick,
+ isReadOnlySorted,
+ columnConfig,
+ visibleColumns,
+ formatFactory,
+ onColumnResize,
+ onColumnHide,
+ alignments,
+ ]
+ );
+
const trailingControlColumns: EuiDataGridControlColumn[] = useMemo(() => {
if (!hasAtLeastOneRowClickAction || !onRowContextMenuClick) {
return [];
From d9905893b9349ac03079e9c119eb8e106601b625 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Casper=20H=C3=BCbertz?=
Date: Tue, 15 Jun 2021 10:40:10 +0200
Subject: [PATCH 34/91] [Observability] Change sortkey navigation in APM and
Uptime (#102134)
* [APM] Change navigation registry sortkey
* [Uptime] Change navigation registry sortkey
---
x-pack/plugins/apm/public/plugin.ts | 4 ++--
x-pack/plugins/uptime/public/apps/plugin.ts | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts
index bfc0a3daf6f0e..77e7f2834b080 100644
--- a/x-pack/plugins/apm/public/plugin.ts
+++ b/x-pack/plugins/apm/public/plugin.ts
@@ -107,7 +107,7 @@ export class ApmPlugin implements Plugin {
// APM navigation
{
label: 'APM',
- sortKey: 200,
+ sortKey: 400,
entries: [
{ label: servicesTitle, app: 'apm', path: '/services' },
{ label: tracesTitle, app: 'apm', path: '/traces' },
@@ -118,7 +118,7 @@ export class ApmPlugin implements Plugin {
// UX navigation
{
label: 'User Experience',
- sortKey: 201,
+ sortKey: 600,
entries: [
{
label: i18n.translate('xpack.apm.ux.overview.heading', {
diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts
index c34da1bc097bc..01fa320bbc789 100644
--- a/x-pack/plugins/uptime/public/apps/plugin.ts
+++ b/x-pack/plugins/uptime/public/apps/plugin.ts
@@ -111,7 +111,7 @@ export class UptimePlugin
return [
{
label: 'Uptime',
- sortKey: 200,
+ sortKey: 500,
entries: [
{
label: i18n.translate('xpack.uptime.overview.heading', {
From 6456c49e2c3e09865ab4249834ed0ebc96ab5d22 Mon Sep 17 00:00:00 2001
From: Thomas Neirynck
Date: Tue, 15 Jun 2021 11:13:27 +0200
Subject: [PATCH 35/91] [Maps] Remove jitter from bounds-coordinates (#100717)
---
.../elasticsearch_geo_utils.ts | 16 +++++++---
.../maps/public/actions/map_actions.test.js | 30 +++++++++++--------
.../maps/public/actions/map_actions.ts | 5 +++-
.../apps/maps/blended_vector_layer.js | 2 +-
4 files changed, 34 insertions(+), 19 deletions(-)
diff --git a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts
index 8033f8d187fd5..edadc20a595ce 100644
--- a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts
+++ b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts
@@ -374,11 +374,19 @@ export function clamp(val: number, min: number, max: number): number {
export function scaleBounds(bounds: MapExtent, scaleFactor: number): MapExtent {
const width = bounds.maxLon - bounds.minLon;
const height = bounds.maxLat - bounds.minLat;
+
+ const newMinLon = bounds.minLon - width * scaleFactor;
+ const nexMaxLon = bounds.maxLon + width * scaleFactor;
+
+ const lonDelta = nexMaxLon - newMinLon;
+ const left = lonDelta > 360 ? -180 : newMinLon;
+ const right = lonDelta > 360 ? 180 : nexMaxLon;
+
return {
- minLon: bounds.minLon - width * scaleFactor,
- minLat: bounds.minLat - height * scaleFactor,
- maxLon: bounds.maxLon + width * scaleFactor,
- maxLat: bounds.maxLat + height * scaleFactor,
+ minLon: left,
+ minLat: clampToLatBounds(bounds.minLat - height * scaleFactor),
+ maxLon: right,
+ maxLat: clampToLonBounds(bounds.maxLat + height * scaleFactor),
};
}
diff --git a/x-pack/plugins/maps/public/actions/map_actions.test.js b/x-pack/plugins/maps/public/actions/map_actions.test.js
index 77ce23594447f..fa69dad616747 100644
--- a/x-pack/plugins/maps/public/actions/map_actions.test.js
+++ b/x-pack/plugins/maps/public/actions/map_actions.test.js
@@ -33,7 +33,9 @@ describe('map_actions', () => {
describe('store mapState is empty', () => {
beforeEach(() => {
require('../selectors/map_selectors').getDataFilters = () => {
- return {};
+ return {
+ zoom: 5,
+ };
};
require('../selectors/map_selectors').getLayerList = () => {
@@ -61,11 +63,13 @@ describe('map_actions', () => {
minLat: 5,
minLon: 95,
},
+ zoom: 5,
});
await action(dispatchMock, getStoreMock);
expect(dispatchMock).toHaveBeenCalledWith({
mapState: {
+ zoom: 5,
extent: {
maxLat: 10,
maxLon: 100,
@@ -73,10 +77,10 @@ describe('map_actions', () => {
minLon: 95,
},
buffer: {
- maxLat: 12.5,
- maxLon: 102.5,
- minLat: 2.5,
- minLon: 92.5,
+ maxLat: 21.94305,
+ maxLon: 112.5,
+ minLat: 0,
+ minLon: 90,
},
},
type: 'MAP_EXTENT_CHANGED',
@@ -154,10 +158,10 @@ describe('map_actions', () => {
minLon: 85,
},
buffer: {
- maxLat: 7.5,
- maxLon: 92.5,
- minLat: -2.5,
- minLon: 82.5,
+ maxLat: 7.71099,
+ maxLon: 92.8125,
+ minLat: -2.81137,
+ minLon: 82.26563,
},
},
type: 'MAP_EXTENT_CHANGED',
@@ -186,10 +190,10 @@ describe('map_actions', () => {
minLon: 96,
},
buffer: {
- maxLat: 13.5,
- maxLon: 103.5,
- minLat: 3.5,
- minLon: 93.5,
+ maxLat: 13.58192,
+ maxLon: 103.53516,
+ minLat: 3.33795,
+ minLon: 93.33984,
},
},
type: 'MAP_EXTENT_CHANGED',
diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts
index 42ce96d102d7e..3cdc5bf05ccee 100644
--- a/x-pack/plugins/maps/public/actions/map_actions.ts
+++ b/x-pack/plugins/maps/public/actions/map_actions.ts
@@ -56,6 +56,7 @@ import {
import { INITIAL_LOCATION } from '../../common/constants';
import { scaleBounds } from '../../common/elasticsearch_util';
import { cleanTooltipStateForLayer } from './tooltip_actions';
+import { expandToTileBoundaries } from '../../common/geo_tile_utils';
export interface MapExtentState {
zoom: number;
@@ -158,7 +159,9 @@ export function mapExtentChanged(mapExtentState: MapExtentState) {
}
if (!doesBufferContainExtent || currentZoom !== newZoom) {
- dataFilters.buffer = scaleBounds(extent, 0.5);
+ const expandedExtent = scaleBounds(extent, 0.5);
+ // snap to the smallest tile-bounds, to avoid jitter in the bounds
+ dataFilters.buffer = expandToTileBoundaries(expandedExtent, Math.ceil(newZoom));
}
}
diff --git a/x-pack/test/functional/apps/maps/blended_vector_layer.js b/x-pack/test/functional/apps/maps/blended_vector_layer.js
index e207410eb2281..3cc7d8e07d623 100644
--- a/x-pack/test/functional/apps/maps/blended_vector_layer.js
+++ b/x-pack/test/functional/apps/maps/blended_vector_layer.js
@@ -29,7 +29,7 @@ export default function ({ getPageObjects, getService }) {
it('should request documents when zoomed to smaller regions showing less data', async () => {
const { rawResponse: response } = await PageObjects.maps.getResponse();
// Allow a range of hits to account for variances in browser window size.
- expect(response.hits.hits.length).to.be.within(30, 40);
+ expect(response.hits.hits.length).to.be.within(35, 45);
});
it('should request clusters when zoomed to larger regions showing lots of data', async () => {
From 2ef66c139adb97566acc21a5f1ba110638c0a711 Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Tue, 15 Jun 2021 11:48:08 +0200
Subject: [PATCH 36/91] [Lens] Add reset and hide actions to columns on
dashboard (#102025)
---
.../__snapshots__/table_basic.test.tsx.snap | 65 +++++++++++++++++--
.../components/columns.tsx | 42 ++++++------
.../components/table_basic.test.tsx | 2 +-
.../components/table_basic.tsx | 10 ++-
4 files changed, 91 insertions(+), 28 deletions(-)
diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap b/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap
index 9ad39e1561e97..7e3c8c3342e4c 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap
+++ b/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap
@@ -487,7 +487,7 @@ exports[`DatatableComponent it renders the title and value 1`] = `
`;
-exports[`DatatableComponent it should not render actions on header when it is in read only mode 1`] = `
+exports[`DatatableComponent it should render hide and reset actions on header even when it is in read only mode 1`] = `
onColumnResize({ columnId: originalColumnId || field, width: undefined }),
+ iconType: 'empty',
+ label: i18n.translate('xpack.lens.table.resize.reset', {
+ defaultMessage: 'Reset width',
+ }),
+ 'data-test-subj': 'lensDatatableResetWidth',
+ isDisabled: initialWidth == null,
+ });
+ if (!isTransposed) {
additionalActions.push({
color: 'text',
size: 'xs',
- onClick: () => onColumnResize({ columnId: originalColumnId || field, width: undefined }),
- iconType: 'empty',
- label: i18n.translate('xpack.lens.table.resize.reset', {
- defaultMessage: 'Reset width',
+ onClick: () => onColumnHide({ columnId: originalColumnId || field }),
+ iconType: 'eyeClosed',
+ label: i18n.translate('xpack.lens.table.hide.hideLabel', {
+ defaultMessage: 'Hide',
}),
- 'data-test-subj': 'lensDatatableResetWidth',
- isDisabled: initialWidth == null,
+ 'data-test-subj': 'lensDatatableHide',
+ isDisabled: !isHidden && visibleColumns.length <= 1,
});
- if (!isTransposed) {
- additionalActions.push({
- color: 'text',
- size: 'xs',
- onClick: () => onColumnHide({ columnId: originalColumnId || field }),
- iconType: 'eyeClosed',
- label: i18n.translate('xpack.lens.table.hide.hideLabel', {
- defaultMessage: 'Hide',
- }),
- 'data-test-subj': 'lensDatatableHide',
- isDisabled: !isHidden && visibleColumns.length <= 1,
- });
- } else if (columnArgs?.bucketValues) {
+ }
+
+ if (!isReadOnly) {
+ if (isTransposed && columnArgs?.bucketValues) {
const bucketValues = columnArgs?.bucketValues;
additionalActions.push({
color: 'text',
diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx
index 0ec6f832cd642..972ef99d7d7f6 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx
+++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx
@@ -129,7 +129,7 @@ describe('DatatableComponent', () => {
).toMatchSnapshot();
});
- test('it should not render actions on header when it is in read only mode', () => {
+ test('it should render hide and reset actions on header even when it is in read only mode', () => {
const { data, args } = sampleArgs();
expect(
diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx
index 2a6b4879854d7..b48cb94563d3b 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx
+++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx
@@ -280,9 +280,13 @@ export const DatatableComponent = (props: DatatableRenderProps) => {
[formatters, columnConfig, props.uiSettings]
);
- const columnVisibility = useMemo(() => ({ visibleColumns, setVisibleColumns: () => {} }), [
- visibleColumns,
- ]);
+ const columnVisibility = useMemo(
+ () => ({
+ visibleColumns,
+ setVisibleColumns: () => {},
+ }),
+ [visibleColumns]
+ );
const sorting = useMemo(
() => createGridSortingConfig(sortBy, sortDirection as LensGridDirection, onEditAction),
From f1b6fe04ed0ddbd4117bbea1c61467f0277817df Mon Sep 17 00:00:00 2001
From: Matthew Kime
Date: Tue, 15 Jun 2021 05:12:43 -0500
Subject: [PATCH 37/91] [Index Patterns] Move rollup config to index pattern
management (#102145)
* move rollup config to index pattern management
---
.../public/constants.ts | 9 +++++++
.../index_pattern_management/public/mocks.ts | 9 +------
.../index_pattern_management/public/plugin.ts | 5 +++-
.../components/rollup_prompt/index.ts | 5 ++--
.../rollup_prompt/rollup_prompt.tsx | 9 ++++---
.../public/service/creation/index.ts | 2 ++
.../creation/rollup_creation_config.js | 21 ++++++++-------
.../index_pattern_management_service.ts | 27 +++++++++++++------
.../public/service/list/index.ts | 2 ++
.../public/service/list/rollup_list_config.js | 7 ++---
x-pack/plugins/rollup/kibana.json | 1 -
x-pack/plugins/rollup/public/plugin.ts | 19 ++-----------
x-pack/plugins/rollup/tsconfig.json | 1 -
.../translations/translations/ja-JP.json | 18 ++++++-------
.../translations/translations/zh-CN.json | 18 ++++++-------
15 files changed, 80 insertions(+), 73 deletions(-)
create mode 100644 src/plugins/index_pattern_management/public/constants.ts
rename x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/index.js => src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/index.ts (53%)
rename x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js => src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/rollup_prompt.tsx (76%)
rename x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js => src/plugins/index_pattern_management/public/service/creation/rollup_creation_config.js (84%)
rename x-pack/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js => src/plugins/index_pattern_management/public/service/list/rollup_list_config.js (86%)
diff --git a/src/plugins/index_pattern_management/public/constants.ts b/src/plugins/index_pattern_management/public/constants.ts
new file mode 100644
index 0000000000000..e5010d133f0f3
--- /dev/null
+++ b/src/plugins/index_pattern_management/public/constants.ts
@@ -0,0 +1,9 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export const CONFIG_ROLLUPS = 'rollups:enableIndexPatterns';
diff --git a/src/plugins/index_pattern_management/public/mocks.ts b/src/plugins/index_pattern_management/public/mocks.ts
index 6c709fb14f08d..7671a532d1cb8 100644
--- a/src/plugins/index_pattern_management/public/mocks.ts
+++ b/src/plugins/index_pattern_management/public/mocks.ts
@@ -19,14 +19,7 @@ import {
} from './plugin';
import { IndexPatternManagmentContext } from './types';
-const createSetupContract = (): IndexPatternManagementSetup => ({
- creation: {
- addCreationConfig: jest.fn(),
- } as any,
- list: {
- addListConfig: jest.fn(),
- } as any,
-});
+const createSetupContract = (): IndexPatternManagementSetup => {};
const createStartContract = (): IndexPatternManagementStart => ({
creation: {
diff --git a/src/plugins/index_pattern_management/public/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts
index e3c156927bfac..610b3541620b0 100644
--- a/src/plugins/index_pattern_management/public/plugin.ts
+++ b/src/plugins/index_pattern_management/public/plugin.ts
@@ -81,7 +81,10 @@ export class IndexPatternManagementPlugin
},
});
- return this.indexPatternManagementService.setup({ httpClient: core.http });
+ return this.indexPatternManagementService.setup({
+ httpClient: core.http,
+ uiSettings: core.uiSettings,
+ });
}
public start(core: CoreStart, plugins: IndexPatternManagementStartDependencies) {
diff --git a/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/index.js b/src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/index.ts
similarity index 53%
rename from x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/index.js
rename to src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/index.ts
index 1d9eff8227c0a..d1fc2fa242eb1 100644
--- a/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/index.js
+++ b/src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/index.ts
@@ -1,8 +1,9 @@
/*
* 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.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
export { RollupPrompt } from './rollup_prompt';
diff --git a/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js b/src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/rollup_prompt.tsx
similarity index 76%
rename from x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js
rename to src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/rollup_prompt.tsx
index 9306ab082dff4..81fcdaedb90c9 100644
--- a/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js
+++ b/src/plugins/index_pattern_management/public/service/creation/components/rollup_prompt/rollup_prompt.tsx
@@ -1,8 +1,9 @@
/*
* 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.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import React from 'react';
@@ -14,7 +15,7 @@ export const RollupPrompt = () => (
{i18n.translate(
- 'xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text',
+ 'indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text',
{
defaultMessage:
"Kibana's support for rollup index patterns is in beta. You might encounter issues using " +
@@ -25,7 +26,7 @@ export const RollupPrompt = () => (
{i18n.translate(
- 'xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text',
+ 'indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text',
{
defaultMessage:
'You can match a rollup index pattern against one rollup index and zero or more regular ' +
diff --git a/src/plugins/index_pattern_management/public/service/creation/index.ts b/src/plugins/index_pattern_management/public/service/creation/index.ts
index 51610bc83e371..e1f464b01e550 100644
--- a/src/plugins/index_pattern_management/public/service/creation/index.ts
+++ b/src/plugins/index_pattern_management/public/service/creation/index.ts
@@ -8,3 +8,5 @@
export { IndexPatternCreationConfig, IndexPatternCreationOption } from './config';
export { IndexPatternCreationManager } from './manager';
+// @ts-ignore
+export { RollupIndexPatternCreationConfig } from './rollup_creation_config';
diff --git a/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js b/src/plugins/index_pattern_management/public/service/creation/rollup_creation_config.js
similarity index 84%
rename from x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js
rename to src/plugins/index_pattern_management/public/service/creation/rollup_creation_config.js
index 8e5203fca9034..2a85dfa01143c 100644
--- a/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js
+++ b/src/plugins/index_pattern_management/public/service/creation/rollup_creation_config.js
@@ -1,43 +1,44 @@
/*
* 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.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
import { RollupPrompt } from './components/rollup_prompt';
-import { IndexPatternCreationConfig } from '../../../../../src/plugins/index_pattern_management/public';
+import { IndexPatternCreationConfig } from '.';
const rollupIndexPatternTypeName = i18n.translate(
- 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultTypeName',
+ 'indexPatternManagement.editRollupIndexPattern.createIndex.defaultTypeName',
{ defaultMessage: 'rollup index pattern' }
);
const rollupIndexPatternButtonText = i18n.translate(
- 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonText',
+ 'indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonText',
{ defaultMessage: 'Rollup index pattern' }
);
const rollupIndexPatternButtonDescription = i18n.translate(
- 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonDescription',
+ 'indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonDescription',
{ defaultMessage: 'Perform limited aggregations against summarized data' }
);
const rollupIndexPatternNoMatchError = i18n.translate(
- 'xpack.rollupJobs.editRollupIndexPattern.createIndex.noMatchError',
+ 'indexPatternManagement.editRollupIndexPattern.createIndex.noMatchError',
{ defaultMessage: 'Rollup index pattern error: must match one rollup index' }
);
const rollupIndexPatternTooManyMatchesError = i18n.translate(
- 'xpack.rollupJobs.editRollupIndexPattern.createIndex.tooManyMatchesError',
+ 'indexPatternManagement.editRollupIndexPattern.createIndex.tooManyMatchesError',
{ defaultMessage: 'Rollup index pattern error: can only match one rollup index' }
);
const rollupIndexPatternIndexLabel = i18n.translate(
- 'xpack.rollupJobs.editRollupIndexPattern.createIndex.indexLabel',
+ 'indexPatternManagement.editRollupIndexPattern.createIndex.indexLabel',
{ defaultMessage: 'Rollup' }
);
@@ -127,7 +128,7 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig
if (error) {
const errorMessage = i18n.translate(
- 'xpack.rollupJobs.editRollupIndexPattern.createIndex.uncaughtError',
+ 'indexPatternManagement.editRollupIndexPattern.createIndex.uncaughtError',
{
defaultMessage: 'Rollup index pattern error: {error}',
values: {
diff --git a/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts
index f30ccfcb9f3ed..19346dbf31d18 100644
--- a/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts
+++ b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts
@@ -6,11 +6,22 @@
* Side Public License, v 1.
*/
-import { HttpSetup } from '../../../../core/public';
-import { IndexPatternCreationManager, IndexPatternCreationConfig } from './creation';
-import { IndexPatternListManager, IndexPatternListConfig } from './list';
+import { HttpSetup, CoreSetup } from '../../../../core/public';
+import {
+ IndexPatternCreationManager,
+ IndexPatternCreationConfig,
+ RollupIndexPatternCreationConfig,
+} from './creation';
+import {
+ IndexPatternListManager,
+ IndexPatternListConfig,
+ RollupIndexPatternListConfig,
+} from './list';
+
+import { CONFIG_ROLLUPS } from '../constants';
interface SetupDependencies {
httpClient: HttpSetup;
+ uiSettings: CoreSetup['uiSettings'];
}
/**
@@ -27,17 +38,17 @@ export class IndexPatternManagementService {
this.indexPatternListConfig = new IndexPatternListManager();
}
- public setup({ httpClient }: SetupDependencies) {
+ public setup({ httpClient, uiSettings }: SetupDependencies) {
const creationManagerSetup = this.indexPatternCreationManager.setup(httpClient);
creationManagerSetup.addCreationConfig(IndexPatternCreationConfig);
const indexPatternListConfigSetup = this.indexPatternListConfig.setup();
indexPatternListConfigSetup.addListConfig(IndexPatternListConfig);
- return {
- creation: creationManagerSetup,
- list: indexPatternListConfigSetup,
- };
+ if (uiSettings.get(CONFIG_ROLLUPS)) {
+ creationManagerSetup.addCreationConfig(RollupIndexPatternCreationConfig);
+ indexPatternListConfigSetup.addListConfig(RollupIndexPatternListConfig);
+ }
}
public start() {
diff --git a/src/plugins/index_pattern_management/public/service/list/index.ts b/src/plugins/index_pattern_management/public/service/list/index.ts
index 620d4c7600733..738b807ac7624 100644
--- a/src/plugins/index_pattern_management/public/service/list/index.ts
+++ b/src/plugins/index_pattern_management/public/service/list/index.ts
@@ -8,3 +8,5 @@
export { IndexPatternListConfig } from './config';
export { IndexPatternListManager } from './manager';
+// @ts-ignore
+export { RollupIndexPatternListConfig } from './rollup_list_config';
diff --git a/x-pack/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js b/src/plugins/index_pattern_management/public/service/list/rollup_list_config.js
similarity index 86%
rename from x-pack/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js
rename to src/plugins/index_pattern_management/public/service/list/rollup_list_config.js
index 43eee6ca27f9a..9a80d5fd0d622 100644
--- a/x-pack/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js
+++ b/src/plugins/index_pattern_management/public/service/list/rollup_list_config.js
@@ -1,11 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
-import { IndexPatternListConfig } from '../../../../../src/plugins/index_pattern_management/public';
+import { IndexPatternListConfig } from '.';
function isRollup(indexPattern) {
return (
diff --git a/x-pack/plugins/rollup/kibana.json b/x-pack/plugins/rollup/kibana.json
index 725b563c3674f..10541d9a4ebdd 100644
--- a/x-pack/plugins/rollup/kibana.json
+++ b/x-pack/plugins/rollup/kibana.json
@@ -5,7 +5,6 @@
"server": true,
"ui": true,
"requiredPlugins": [
- "indexPatternManagement",
"management",
"licensing",
"features"
diff --git a/x-pack/plugins/rollup/public/plugin.ts b/x-pack/plugins/rollup/public/plugin.ts
index 17e352e1a4472..0d345e326193c 100644
--- a/x-pack/plugins/rollup/public/plugin.ts
+++ b/x-pack/plugins/rollup/public/plugin.ts
@@ -12,14 +12,13 @@ import { rollupBadgeExtension, rollupToggleExtension } from './extend_index_mana
import { RollupIndexPatternCreationConfig } from './index_pattern_creation/rollup_index_pattern_creation_config';
// @ts-ignore
import { RollupIndexPatternListConfig } from './index_pattern_list/rollup_index_pattern_list_config';
-import { CONFIG_ROLLUPS, UIM_APP_NAME } from '../common';
+import { UIM_APP_NAME } from '../common';
import {
FeatureCatalogueCategory,
HomePublicPluginSetup,
} from '../../../../src/plugins/home/public';
import { ManagementSetup } from '../../../../src/plugins/management/public';
import { IndexManagementPluginSetup } from '../../index_management/public';
-import { IndexPatternManagementSetup } from '../../../../src/plugins/index_pattern_management/public';
// @ts-ignore
import { setHttp, init as initDocumentation } from './crud_app/services/index';
import { setNotifications, setFatalErrors, setUiStatsReporter } from './kibana_services';
@@ -29,20 +28,13 @@ export interface RollupPluginSetupDependencies {
home?: HomePublicPluginSetup;
management: ManagementSetup;
indexManagement?: IndexManagementPluginSetup;
- indexPatternManagement: IndexPatternManagementSetup;
usageCollection?: UsageCollectionSetup;
}
export class RollupPlugin implements Plugin {
setup(
core: CoreSetup,
- {
- home,
- management,
- indexManagement,
- indexPatternManagement,
- usageCollection,
- }: RollupPluginSetupDependencies
+ { home, management, indexManagement, usageCollection }: RollupPluginSetupDependencies
) {
setFatalErrors(core.fatalErrors);
if (usageCollection) {
@@ -54,13 +46,6 @@ export class RollupPlugin implements Plugin {
indexManagement.extensionsService.addToggle(rollupToggleExtension);
}
- const isRollupIndexPatternsEnabled = core.uiSettings.get(CONFIG_ROLLUPS);
-
- if (isRollupIndexPatternsEnabled) {
- indexPatternManagement.creation.addCreationConfig(RollupIndexPatternCreationConfig);
- indexPatternManagement.list.addListConfig(RollupIndexPatternListConfig);
- }
-
if (home) {
home.featureCatalogue.register({
id: 'rollup_jobs',
diff --git a/x-pack/plugins/rollup/tsconfig.json b/x-pack/plugins/rollup/tsconfig.json
index 9b994d1710ffc..6885081ce4bdd 100644
--- a/x-pack/plugins/rollup/tsconfig.json
+++ b/x-pack/plugins/rollup/tsconfig.json
@@ -16,7 +16,6 @@
"references": [
{ "path": "../../../src/core/tsconfig.json" },
// required plugins
- { "path": "../../../src/plugins/index_pattern_management/tsconfig.json" },
{ "path": "../../../src/plugins/management/tsconfig.json" },
{ "path": "../licensing/tsconfig.json" },
{ "path": "../features/tsconfig.json" },
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 74a0154c3242b..7409000156ae2 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -18030,15 +18030,15 @@
"xpack.rollupJobs.detailPanel.jobActionMenu.buttonLabel": "管理",
"xpack.rollupJobs.detailPanel.loadingLabel": "ロールアップジョブを読み込み中...",
"xpack.rollupJobs.detailPanel.notFoundLabel": "ロールアップジョブが見つかりません",
- "xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonDescription": "要約データに制限された集約を実行します。",
- "xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonText": "ロールアップインデックスパターン",
- "xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultTypeName": "ロールアップインデックスパターン",
- "xpack.rollupJobs.editRollupIndexPattern.createIndex.indexLabel": "ロールアップ",
- "xpack.rollupJobs.editRollupIndexPattern.createIndex.noMatchError": "ロールアップインデックスパターンエラー:ロールアップインデックスの 1 つと一致している必要があります",
- "xpack.rollupJobs.editRollupIndexPattern.createIndex.tooManyMatchesError": "ロールアップインデックスパターンエラー:一致できるロールアップインデックスは 1 つだけです",
- "xpack.rollupJobs.editRollupIndexPattern.createIndex.uncaughtError": "ロールアップインデックスパターンエラー:{error}",
- "xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text": "ロールアップインデックスパターンのKibanaのサポートはベータ版です。保存された検索、可視化、ダッシュボードでこれらのパターンを使用すると問題が発生する場合があります。Timelionや機械学習などの一部の高度な機能ではサポートされていません。",
- "xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text": "ロールアップインデックスパターンは、1つのロールアップインデックスとゼロ以上の標準インデックスと一致させることができます。ロールアップインデックスパターンでは、メトリック、フィールド、間隔、アグリゲーションが制限されています。ロールアップインデックスは、1つのジョブ構成があるインデックス、または複数のジョブと互換する構成があるインデックスに制限されています。",
+ "indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonDescription": "要約データに制限された集約を実行します。",
+ "indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonText": "ロールアップインデックスパターン",
+ "indexPatternManagement.editRollupIndexPattern.createIndex.defaultTypeName": "ロールアップインデックスパターン",
+ "indexPatternManagement.editRollupIndexPattern.createIndex.indexLabel": "ロールアップ",
+ "indexPatternManagement.editRollupIndexPattern.createIndex.noMatchError": "ロールアップインデックスパターンエラー:ロールアップインデックスの 1 つと一致している必要があります",
+ "indexPatternManagement.editRollupIndexPattern.createIndex.tooManyMatchesError": "ロールアップインデックスパターンエラー:一致できるロールアップインデックスは 1 つだけです",
+ "indexPatternManagement.editRollupIndexPattern.createIndex.uncaughtError": "ロールアップインデックスパターンエラー:{error}",
+ "indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text": "ロールアップインデックスパターンのKibanaのサポートはベータ版です。保存された検索、可視化、ダッシュボードでこれらのパターンを使用すると問題が発生する場合があります。Timelionや機械学習などの一部の高度な機能ではサポートされていません。",
+ "indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text": "ロールアップインデックスパターンは、1つのロールアップインデックスとゼロ以上の標準インデックスと一致させることができます。ロールアップインデックスパターンでは、メトリック、フィールド、間隔、アグリゲーションが制限されています。ロールアップインデックスは、1つのジョブ構成があるインデックス、または複数のジョブと互換する構成があるインデックスに制限されています。",
"xpack.rollupJobs.featureCatalogueDescription": "今後の分析用に履歴データを小さなインデックスに要約して格納します。",
"xpack.rollupJobs.indexMgmtBadge.rollupLabel": "ロールアップ",
"xpack.rollupJobs.indexMgmtToggle.toggleLabel": "ロールアップインデックスを含める",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index aebee851043b6..72a0d5f26b423 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -18270,15 +18270,15 @@
"xpack.rollupJobs.detailPanel.jobActionMenu.buttonLabel": "管理",
"xpack.rollupJobs.detailPanel.loadingLabel": "正在加载汇总/打包作业……",
"xpack.rollupJobs.detailPanel.notFoundLabel": "未找到汇总/打包作业",
- "xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonDescription": "针对汇总数据执行有限聚合",
- "xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonText": "汇总/打包索引模式",
- "xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultTypeName": "汇总/打包索引模式",
- "xpack.rollupJobs.editRollupIndexPattern.createIndex.indexLabel": "汇总/打包",
- "xpack.rollupJobs.editRollupIndexPattern.createIndex.noMatchError": "汇总/打包索引模式错误:必须匹配一个汇总/打包索引",
- "xpack.rollupJobs.editRollupIndexPattern.createIndex.tooManyMatchesError": "汇总/打包索引模式错误:只能匹配一个汇总/打包索引",
- "xpack.rollupJobs.editRollupIndexPattern.createIndex.uncaughtError": "汇总索引模式错误:{error}",
- "xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text": "Kibana 对汇总/打包索引模式的支持处于公测版状态。将这些模式用于已保存搜索、可视化以及仪表板可能会遇到问题。某些高级功能,如 Timelion 和 Machine Learning,不支持这些模式。",
- "xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text": "可以根据一个汇总/打包索引和零个或更多常规索引匹配汇总/打包索引模式。汇总/打包索引模式的指标、字段、时间间隔和聚合有限。汇总/打包索引仅限于具有一个作业配置或多个作业配置兼容的索引。",
+ "indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonDescription": "针对汇总数据执行有限聚合",
+ "indexPatternManagement.editRollupIndexPattern.createIndex.defaultButtonText": "汇总/打包索引模式",
+ "indexPatternManagement.editRollupIndexPattern.createIndex.defaultTypeName": "汇总/打包索引模式",
+ "indexPatternManagement.editRollupIndexPattern.createIndex.indexLabel": "汇总/打包",
+ "indexPatternManagement.editRollupIndexPattern.createIndex.noMatchError": "汇总/打包索引模式错误:必须匹配一个汇总/打包索引",
+ "indexPatternManagement.editRollupIndexPattern.createIndex.tooManyMatchesError": "汇总/打包索引模式错误:只能匹配一个汇总/打包索引",
+ "indexPatternManagement.editRollupIndexPattern.createIndex.uncaughtError": "汇总索引模式错误:{error}",
+ "indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text": "Kibana 对汇总/打包索引模式的支持处于公测版状态。将这些模式用于已保存搜索、可视化以及仪表板可能会遇到问题。某些高级功能,如 Timelion 和 Machine Learning,不支持这些模式。",
+ "indexPatternManagement.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text": "可以根据一个汇总/打包索引和零个或更多常规索引匹配汇总/打包索引模式。汇总/打包索引模式的指标、字段、时间间隔和聚合有限。汇总/打包索引仅限于具有一个作业配置或多个作业配置兼容的索引。",
"xpack.rollupJobs.featureCatalogueDescription": "汇总历史数据并将其存储在较小的索引中以供将来分析。",
"xpack.rollupJobs.indexMgmtBadge.rollupLabel": "汇总/打包",
"xpack.rollupJobs.indexMgmtToggle.toggleLabel": "包括汇总索引",
From 0d6ce5da276cc67aecd94bf92d7f9feb969f4eb7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?=
Date: Tue, 15 Jun 2021 12:38:52 +0200
Subject: [PATCH 38/91] [Uptime] Skip flaky test (#102163)
---
x-pack/test/functional/apps/uptime/synthetics_integration.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/test/functional/apps/uptime/synthetics_integration.ts b/x-pack/test/functional/apps/uptime/synthetics_integration.ts
index 52ec81b8bf7db..0872abfcaa4f8 100644
--- a/x-pack/test/functional/apps/uptime/synthetics_integration.ts
+++ b/x-pack/test/functional/apps/uptime/synthetics_integration.ts
@@ -253,7 +253,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
]);
});
- it('allows configuring http advanced options', async () => {
+ it.skip('allows configuring http advanced options', async () => {
// This test ensures that updates made to the Synthetics Policy are carried all the way through
// to the generated Agent Policy that is dispatch down to the Elastic Agent.
const config = generateHTTPConfig('http://elastic.co');
From 7390d6a45c4b7c375ac3ccf4221555d8c331be0d Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Tue, 15 Jun 2021 12:41:56 +0200
Subject: [PATCH 39/91] [User Experience App] Update to make sure env filter is
being used (#102063)
---
.../__snapshots__/queries.test.ts.snap | 15 +++++++
.../apm/server/lib/rum_client/queries.test.ts | 45 +++++++++++--------
.../rum_client/ui_filters/get_es_filter.ts | 3 +-
.../plugins/apm/server/utils/test_helpers.tsx | 3 +-
4 files changed, 46 insertions(+), 20 deletions(-)
diff --git a/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap
index 79eb0fbce5498..b54e9726667e6 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap
@@ -58,6 +58,11 @@ Object {
"transaction.type": "page-load",
},
},
+ Object {
+ "term": Object {
+ "service.environment": "staging",
+ },
+ },
],
},
},
@@ -492,6 +497,11 @@ Object {
"field": "transaction.marks.navigationTiming.fetchStart",
},
},
+ Object {
+ "term": Object {
+ "service.environment": "staging",
+ },
+ },
],
},
},
@@ -534,6 +544,11 @@ Object {
"transaction.type": "page-load",
},
},
+ Object {
+ "term": Object {
+ "service.environment": "staging",
+ },
+ },
],
},
},
diff --git a/x-pack/plugins/apm/server/lib/rum_client/queries.test.ts b/x-pack/plugins/apm/server/lib/rum_client/queries.test.ts
index aaf6401b9f407..452f451b11f86 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/queries.test.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/queries.test.ts
@@ -16,6 +16,7 @@ import { getRumServices } from './get_rum_services';
import { getLongTaskMetrics } from './get_long_task_metrics';
import { getWebCoreVitals } from './get_web_core_vitals';
import { getJSErrors } from './get_js_errors';
+import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values';
describe('rum client dashboard queries', () => {
let mock: SearchParamsMock;
@@ -25,32 +26,38 @@ describe('rum client dashboard queries', () => {
});
it('fetches client metrics', async () => {
- mock = await inspectSearchParams((setup) =>
- getClientMetrics({
- setup,
- })
+ mock = await inspectSearchParams(
+ (setup) =>
+ getClientMetrics({
+ setup,
+ }),
+ { uiFilters: { environment: 'staging' } }
);
expect(mock.params).toMatchSnapshot();
});
it('fetches page view trends', async () => {
- mock = await inspectSearchParams((setup) =>
- getPageViewTrends({
- setup,
- })
+ mock = await inspectSearchParams(
+ (setup) =>
+ getPageViewTrends({
+ setup,
+ }),
+ { uiFilters: { environment: 'staging' } }
);
expect(mock.params).toMatchSnapshot();
});
it('fetches page load distribution', async () => {
- mock = await inspectSearchParams((setup) =>
- getPageLoadDistribution({
- setup,
- minPercentile: '0',
- maxPercentile: '99',
- })
+ mock = await inspectSearchParams(
+ (setup) =>
+ getPageLoadDistribution({
+ setup,
+ minPercentile: '0',
+ maxPercentile: '99',
+ }),
+ { uiFilters: { environment: 'staging' } }
);
expect(mock.params).toMatchSnapshot();
});
@@ -65,10 +72,12 @@ describe('rum client dashboard queries', () => {
});
it('fetches rum core vitals', async () => {
- mock = await inspectSearchParams((setup) =>
- getWebCoreVitals({
- setup,
- })
+ mock = await inspectSearchParams(
+ (setup) =>
+ getWebCoreVitals({
+ setup,
+ }),
+ { uiFilters: { environment: ENVIRONMENT_ALL.value } }
);
expect(mock.params).toMatchSnapshot();
});
diff --git a/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts
index 43cbb485c4510..6b8cee244a192 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts
@@ -8,6 +8,7 @@
import { ESFilter } from '../../../../../../../typings/elasticsearch';
import { UIFilters } from '../../../../typings/ui_filters';
import { localUIFilters, localUIFilterNames } from './local_ui_filters/config';
+import { environmentQuery } from '../../../utils/queries';
export function getEsFilter(uiFilters: UIFilters) {
const localFilterValues = uiFilters;
@@ -23,5 +24,5 @@ export function getEsFilter(uiFilters: UIFilters) {
};
}) as ESFilter[];
- return mappedFilters;
+ return [...mappedFilters, ...environmentQuery(uiFilters.environment)];
}
diff --git a/x-pack/plugins/apm/server/utils/test_helpers.tsx b/x-pack/plugins/apm/server/utils/test_helpers.tsx
index 9c63f0140bdf2..8e1d971c9cd2e 100644
--- a/x-pack/plugins/apm/server/utils/test_helpers.tsx
+++ b/x-pack/plugins/apm/server/utils/test_helpers.tsx
@@ -17,6 +17,7 @@ interface Options {
mockResponse?: (
request: ESSearchRequest
) => ESSearchResponse;
+ uiFilters?: Record;
}
interface MockSetup {
@@ -86,7 +87,7 @@ export async function inspectSearchParams(
},
}
) as APMConfig,
- uiFilters: {},
+ uiFilters: options?.uiFilters ?? {},
indices: {
/* eslint-disable @typescript-eslint/naming-convention */
'apm_oss.sourcemapIndices': 'myIndex',
From bcb941acea6de3067fd515bb1bad5d484cd4b39e Mon Sep 17 00:00:00 2001
From: Ashokaditya
Date: Tue, 15 Jun 2021 13:26:24 +0200
Subject: [PATCH 40/91] [Security Solution][Endpoint] Actions log entries
display relative time only up to 1 hr from now (#102162)
* show relative time only upto an hr from now
fixes elastic/security-team/issues/1313
* Update formatted_date_time.tsx
undo changes done in e38e202e81f8dd2796bebe9069175d31052c52f2
---
.../common/components/endpoint/formatted_date_time.tsx | 8 ++------
.../endpoint_hosts/view/details/components/log_entry.tsx | 7 +++----
2 files changed, 5 insertions(+), 10 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/formatted_date_time.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/formatted_date_time.tsx
index 740437646f61a..2fdb7e99d860e 100644
--- a/x-pack/plugins/security_solution/public/common/components/endpoint/formatted_date_time.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/endpoint/formatted_date_time.tsx
@@ -8,14 +8,10 @@
import React from 'react';
import { FormattedDate, FormattedTime, FormattedRelative } from '@kbn/i18n/react';
-export const FormattedDateAndTime: React.FC<{ date: Date; showRelativeTime?: boolean }> = ({
- date,
- showRelativeTime = false,
-}) => {
+export const FormattedDateAndTime: React.FC<{ date: Date }> = ({ date }) => {
// If date is greater than or equal to 1h (ago), then show it as a date
- // and if showRelativeTime is false
// else, show it as relative to "now"
- return Date.now() - date.getTime() >= 3.6e6 && !showRelativeTime ? (
+ return Date.now() - date.getTime() >= 3.6e6 ? (
<>
{' @'}
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx
index f8574da7f0a03..c431cd682d25b 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/log_entry.tsx
@@ -10,7 +10,7 @@ import styled from 'styled-components';
import { EuiComment, EuiText, EuiAvatarProps, EuiCommentProps, IconType } from '@elastic/eui';
import { Immutable, ActivityLogEntry } from '../../../../../../../common/endpoint/types';
-import { FormattedDateAndTime } from '../../../../../../common/components/endpoint/formatted_date_time';
+import { FormattedRelativePreferenceDate } from '../../../../../../common/components/formatted_date';
import { LogEntryTimelineIcon } from './log_entry_timeline_icon';
import * as i18 from '../../translations';
@@ -144,9 +144,8 @@ export const LogEntry = memo(({ logEntry }: { logEntry: Immutable{displayResponseEvent ? responseEventTitle : actionEventTitle}}
timelineIcon={
From 8b6608d284e95c4bce11f5ac497ef4b41d59781c Mon Sep 17 00:00:00 2001
From: Jean-Louis Leysens
Date: Tue, 15 Jun 2021 13:56:41 +0200
Subject: [PATCH 41/91] [Screenshot Mode] Prevent newsfeed plugin from fetching
items (#101256)
* - updated newsfeed to depend on low-level screenshotmode plugin
- added new NeverFetch driver that will never resolve to true
for new fetch requests
- added plugin-level test for expected behaviour
* added screenshotmode contract to start!
* address pr feedback
* removed old test and updated test description
* update use of jest.fn to tidy up code
* simplify jest tests and remove unnecessary async
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
src/plugins/newsfeed/kibana.json | 3 +-
.../newsfeed/public/lib/api.test.mocks.ts | 5 ++
src/plugins/newsfeed/public/lib/api.test.ts | 24 +++++-
src/plugins/newsfeed/public/lib/api.ts | 21 +++--
src/plugins/newsfeed/public/lib/driver.ts | 3 +-
.../newsfeed/public/lib/never_fetch_driver.ts | 25 ++++++
src/plugins/newsfeed/public/lib/types.ts | 19 +++++
src/plugins/newsfeed/public/plugin.test.ts | 76 +++++++++++++++++++
src/plugins/newsfeed/public/plugin.tsx | 15 ++--
src/plugins/newsfeed/public/types.ts | 4 +
src/plugins/newsfeed/tsconfig.json | 10 +--
src/plugins/screenshot_mode/public/index.ts | 2 +-
.../screenshot_mode/public/plugin.test.ts | 8 +-
src/plugins/screenshot_mode/public/plugin.ts | 14 ++--
src/plugins/screenshot_mode/public/types.ts | 1 +
15 files changed, 194 insertions(+), 36 deletions(-)
create mode 100644 src/plugins/newsfeed/public/lib/never_fetch_driver.ts
create mode 100644 src/plugins/newsfeed/public/lib/types.ts
create mode 100644 src/plugins/newsfeed/public/plugin.test.ts
diff --git a/src/plugins/newsfeed/kibana.json b/src/plugins/newsfeed/kibana.json
index b9f37b67f6921..0e7ae7cd11c35 100644
--- a/src/plugins/newsfeed/kibana.json
+++ b/src/plugins/newsfeed/kibana.json
@@ -2,5 +2,6 @@
"id": "newsfeed",
"version": "kibana",
"server": true,
- "ui": true
+ "ui": true,
+ "requiredPlugins": ["screenshotMode"]
}
diff --git a/src/plugins/newsfeed/public/lib/api.test.mocks.ts b/src/plugins/newsfeed/public/lib/api.test.mocks.ts
index 677bc203cbef3..8ac66eae6c2f6 100644
--- a/src/plugins/newsfeed/public/lib/api.test.mocks.ts
+++ b/src/plugins/newsfeed/public/lib/api.test.mocks.ts
@@ -8,6 +8,7 @@
import { storageMock } from './storage.mock';
import { driverMock } from './driver.mock';
+import { NeverFetchNewsfeedApiDriver } from './never_fetch_driver';
export const storageInstanceMock = storageMock.create();
jest.doMock('./storage', () => ({
@@ -18,3 +19,7 @@ export const driverInstanceMock = driverMock.create();
jest.doMock('./driver', () => ({
NewsfeedApiDriver: jest.fn().mockImplementation(() => driverInstanceMock),
}));
+
+jest.doMock('./never_fetch_driver', () => ({
+ NeverFetchNewsfeedApiDriver: jest.fn(() => new NeverFetchNewsfeedApiDriver()),
+}));
diff --git a/src/plugins/newsfeed/public/lib/api.test.ts b/src/plugins/newsfeed/public/lib/api.test.ts
index a4894573932e6..58d06e72cd77c 100644
--- a/src/plugins/newsfeed/public/lib/api.test.ts
+++ b/src/plugins/newsfeed/public/lib/api.test.ts
@@ -7,12 +7,16 @@
*/
import { driverInstanceMock, storageInstanceMock } from './api.test.mocks';
+
import moment from 'moment';
import { getApi } from './api';
import { TestScheduler } from 'rxjs/testing';
import { FetchResult, NewsfeedPluginBrowserConfig } from '../types';
import { take } from 'rxjs/operators';
+import { NewsfeedApiDriver as MockNewsfeedApiDriver } from './driver';
+import { NeverFetchNewsfeedApiDriver as MockNeverFetchNewsfeedApiDriver } from './never_fetch_driver';
+
const kibanaVersion = '8.0.0';
const newsfeedId = 'test';
@@ -46,6 +50,8 @@ describe('getApi', () => {
afterEach(() => {
storageInstanceMock.isAnyUnread$.mockReset();
driverInstanceMock.fetchNewsfeedItems.mockReset();
+ (MockNewsfeedApiDriver as jest.Mock).mockClear();
+ (MockNeverFetchNewsfeedApiDriver as jest.Mock).mockClear();
});
it('merges the newsfeed and unread observables', () => {
@@ -60,7 +66,7 @@ describe('getApi', () => {
a: createFetchResult({ feedItems: ['item' as any] }),
})
);
- const api = getApi(createConfig(1000), kibanaVersion, newsfeedId);
+ const api = getApi(createConfig(1000), kibanaVersion, newsfeedId, false);
expectObservable(api.fetchResults$.pipe(take(1))).toBe('(a|)', {
a: createFetchResult({
@@ -83,7 +89,7 @@ describe('getApi', () => {
a: createFetchResult({ feedItems: ['item' as any] }),
})
);
- const api = getApi(createConfig(2), kibanaVersion, newsfeedId);
+ const api = getApi(createConfig(2), kibanaVersion, newsfeedId, false);
expectObservable(api.fetchResults$.pipe(take(2))).toBe('a-(b|)', {
a: createFetchResult({
@@ -111,7 +117,7 @@ describe('getApi', () => {
a: createFetchResult({}),
})
);
- const api = getApi(createConfig(10), kibanaVersion, newsfeedId);
+ const api = getApi(createConfig(10), kibanaVersion, newsfeedId, false);
expectObservable(api.fetchResults$.pipe(take(2))).toBe('a--(b|)', {
a: createFetchResult({
@@ -123,4 +129,16 @@ describe('getApi', () => {
});
});
});
+
+ it('uses the news feed API driver if in not screenshot mode', () => {
+ getApi(createConfig(10), kibanaVersion, newsfeedId, false);
+ expect(MockNewsfeedApiDriver).toHaveBeenCalled();
+ expect(MockNeverFetchNewsfeedApiDriver).not.toHaveBeenCalled();
+ });
+
+ it('uses the never fetch news feed API driver if in not screenshot mode', () => {
+ getApi(createConfig(10), kibanaVersion, newsfeedId, true);
+ expect(MockNewsfeedApiDriver).not.toHaveBeenCalled();
+ expect(MockNeverFetchNewsfeedApiDriver).toHaveBeenCalled();
+ });
});
diff --git a/src/plugins/newsfeed/public/lib/api.ts b/src/plugins/newsfeed/public/lib/api.ts
index 4fbbd8687b73f..7aafc9fd27625 100644
--- a/src/plugins/newsfeed/public/lib/api.ts
+++ b/src/plugins/newsfeed/public/lib/api.ts
@@ -11,6 +11,7 @@ import { map, catchError, filter, mergeMap, tap } from 'rxjs/operators';
import { i18n } from '@kbn/i18n';
import { FetchResult, NewsfeedPluginBrowserConfig } from '../types';
import { NewsfeedApiDriver } from './driver';
+import { NeverFetchNewsfeedApiDriver } from './never_fetch_driver';
import { NewsfeedStorage } from './storage';
export enum NewsfeedApiEndpoint {
@@ -40,13 +41,23 @@ export interface NewsfeedApi {
export function getApi(
config: NewsfeedPluginBrowserConfig,
kibanaVersion: string,
- newsfeedId: string
+ newsfeedId: string,
+ isScreenshotMode: boolean
): NewsfeedApi {
- const userLanguage = i18n.getLocale();
- const fetchInterval = config.fetchInterval.asMilliseconds();
- const mainInterval = config.mainInterval.asMilliseconds();
const storage = new NewsfeedStorage(newsfeedId);
- const driver = new NewsfeedApiDriver(kibanaVersion, userLanguage, fetchInterval, storage);
+ const mainInterval = config.mainInterval.asMilliseconds();
+
+ const createNewsfeedApiDriver = () => {
+ if (isScreenshotMode) {
+ return new NeverFetchNewsfeedApiDriver();
+ }
+
+ const userLanguage = i18n.getLocale();
+ const fetchInterval = config.fetchInterval.asMilliseconds();
+ return new NewsfeedApiDriver(kibanaVersion, userLanguage, fetchInterval, storage);
+ };
+
+ const driver = createNewsfeedApiDriver();
const results$ = timer(0, mainInterval).pipe(
filter(() => driver.shouldFetch()),
diff --git a/src/plugins/newsfeed/public/lib/driver.ts b/src/plugins/newsfeed/public/lib/driver.ts
index 0efa981e8c89d..1762c4a428784 100644
--- a/src/plugins/newsfeed/public/lib/driver.ts
+++ b/src/plugins/newsfeed/public/lib/driver.ts
@@ -10,6 +10,7 @@ import moment from 'moment';
import * as Rx from 'rxjs';
import { NEWSFEED_DEFAULT_SERVICE_BASE_URL } from '../../common/constants';
import { ApiItem, FetchResult, NewsfeedPluginBrowserConfig } from '../types';
+import { INewsfeedApiDriver } from './types';
import { convertItems } from './convert_items';
import type { NewsfeedStorage } from './storage';
@@ -19,7 +20,7 @@ interface NewsfeedResponse {
items: ApiItem[];
}
-export class NewsfeedApiDriver {
+export class NewsfeedApiDriver implements INewsfeedApiDriver {
private readonly kibanaVersion: string;
private readonly loadedTime = moment().utc(); // the date is compared to time in UTC format coming from the service
diff --git a/src/plugins/newsfeed/public/lib/never_fetch_driver.ts b/src/plugins/newsfeed/public/lib/never_fetch_driver.ts
new file mode 100644
index 0000000000000..e95ca9c2d499a
--- /dev/null
+++ b/src/plugins/newsfeed/public/lib/never_fetch_driver.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { Observable } from 'rxjs';
+import { FetchResult } from '../types';
+import { INewsfeedApiDriver } from './types';
+
+/**
+ * NewsfeedApiDriver variant that never fetches results. This is useful for instances where Kibana is started
+ * without any user interaction like when generating a PDF or PNG report.
+ */
+export class NeverFetchNewsfeedApiDriver implements INewsfeedApiDriver {
+ shouldFetch(): boolean {
+ return false;
+ }
+
+ fetchNewsfeedItems(): Observable {
+ throw new Error('Not implemented!');
+ }
+}
diff --git a/src/plugins/newsfeed/public/lib/types.ts b/src/plugins/newsfeed/public/lib/types.ts
new file mode 100644
index 0000000000000..5a62a929eeb7f
--- /dev/null
+++ b/src/plugins/newsfeed/public/lib/types.ts
@@ -0,0 +1,19 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { Observable } from 'rxjs';
+import type { FetchResult, NewsfeedPluginBrowserConfig } from '../types';
+
+export interface INewsfeedApiDriver {
+ /**
+ * Check whether newsfeed items should be (re-)fetched
+ */
+ shouldFetch(): boolean;
+
+ fetchNewsfeedItems(config: NewsfeedPluginBrowserConfig['service']): Observable;
+}
diff --git a/src/plugins/newsfeed/public/plugin.test.ts b/src/plugins/newsfeed/public/plugin.test.ts
new file mode 100644
index 0000000000000..4be69feb79f55
--- /dev/null
+++ b/src/plugins/newsfeed/public/plugin.test.ts
@@ -0,0 +1,76 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { take } from 'rxjs/operators';
+import { coreMock } from '../../../core/public/mocks';
+import { NewsfeedPublicPlugin } from './plugin';
+import { NewsfeedApiEndpoint } from './lib/api';
+
+describe('Newsfeed plugin', () => {
+ let plugin: NewsfeedPublicPlugin;
+
+ beforeAll(() => {
+ jest.useFakeTimers();
+ });
+
+ afterAll(() => {
+ jest.useRealTimers();
+ });
+
+ beforeEach(() => {
+ plugin = new NewsfeedPublicPlugin(coreMock.createPluginInitializerContext());
+ });
+
+ describe('#start', () => {
+ beforeEach(() => {
+ plugin.setup(coreMock.createSetup());
+ });
+
+ beforeEach(() => {
+ /**
+ * We assume for these tests that the newsfeed stream exposed by start will fetch newsfeed items
+ * on the first tick for new subscribers
+ */
+ jest.spyOn(window, 'fetch');
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('base case', () => {
+ it('makes fetch requests', () => {
+ const startContract = plugin.start(coreMock.createStart(), {
+ screenshotMode: { isScreenshotMode: () => false },
+ });
+ const sub = startContract
+ .createNewsFeed$(NewsfeedApiEndpoint.KIBANA) // Any endpoint will do
+ .pipe(take(1))
+ .subscribe(() => {});
+ jest.runOnlyPendingTimers();
+ expect(window.fetch).toHaveBeenCalled();
+ sub.unsubscribe();
+ });
+ });
+
+ describe('when in screenshot mode', () => {
+ it('makes no fetch requests in screenshot mode', () => {
+ const startContract = plugin.start(coreMock.createStart(), {
+ screenshotMode: { isScreenshotMode: () => true },
+ });
+ const sub = startContract
+ .createNewsFeed$(NewsfeedApiEndpoint.KIBANA) // Any endpoint will do
+ .pipe(take(1))
+ .subscribe(() => {});
+ jest.runOnlyPendingTimers();
+ expect(window.fetch).not.toHaveBeenCalled();
+ sub.unsubscribe();
+ });
+ });
+ });
+});
diff --git a/src/plugins/newsfeed/public/plugin.tsx b/src/plugins/newsfeed/public/plugin.tsx
index fdda0a24b8bd5..656fc2ef00bb9 100644
--- a/src/plugins/newsfeed/public/plugin.tsx
+++ b/src/plugins/newsfeed/public/plugin.tsx
@@ -13,7 +13,7 @@ import React from 'react';
import moment from 'moment';
import { I18nProvider } from '@kbn/i18n/react';
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public';
-import { NewsfeedPluginBrowserConfig } from './types';
+import { NewsfeedPluginBrowserConfig, NewsfeedPluginStartDependencies } from './types';
import { NewsfeedNavButton } from './components/newsfeed_header_nav_button';
import { getApi, NewsfeedApi, NewsfeedApiEndpoint } from './lib/api';
@@ -41,8 +41,10 @@ export class NewsfeedPublicPlugin
return {};
}
- public start(core: CoreStart) {
- const api = this.createNewsfeedApi(this.config, NewsfeedApiEndpoint.KIBANA);
+ public start(core: CoreStart, { screenshotMode }: NewsfeedPluginStartDependencies) {
+ const isScreenshotMode = screenshotMode.isScreenshotMode();
+
+ const api = this.createNewsfeedApi(this.config, NewsfeedApiEndpoint.KIBANA, isScreenshotMode);
core.chrome.navControls.registerRight({
order: 1000,
mount: (target) => this.mount(api, target),
@@ -56,7 +58,7 @@ export class NewsfeedPublicPlugin
pathTemplate: `/${endpoint}/v{VERSION}.json`,
},
});
- const { fetchResults$ } = this.createNewsfeedApi(config, endpoint);
+ const { fetchResults$ } = this.createNewsfeedApi(config, endpoint, isScreenshotMode);
return fetchResults$;
},
};
@@ -68,9 +70,10 @@ export class NewsfeedPublicPlugin
private createNewsfeedApi(
config: NewsfeedPluginBrowserConfig,
- newsfeedId: NewsfeedApiEndpoint
+ newsfeedId: NewsfeedApiEndpoint,
+ isScreenshotMode: boolean
): NewsfeedApi {
- const api = getApi(config, this.kibanaVersion, newsfeedId);
+ const api = getApi(config, this.kibanaVersion, newsfeedId, isScreenshotMode);
return {
markAsRead: api.markAsRead,
fetchResults$: api.fetchResults$.pipe(
diff --git a/src/plugins/newsfeed/public/types.ts b/src/plugins/newsfeed/public/types.ts
index cca656565f4ca..a7ff917f6f975 100644
--- a/src/plugins/newsfeed/public/types.ts
+++ b/src/plugins/newsfeed/public/types.ts
@@ -7,6 +7,10 @@
*/
import { Duration, Moment } from 'moment';
+import type { ScreenshotModePluginStart } from 'src/plugins/screenshot_mode/public';
+export interface NewsfeedPluginStartDependencies {
+ screenshotMode: ScreenshotModePluginStart;
+}
// Ideally, we may want to obtain the type from the configSchema and exposeToBrowser keys...
export interface NewsfeedPluginBrowserConfig {
diff --git a/src/plugins/newsfeed/tsconfig.json b/src/plugins/newsfeed/tsconfig.json
index 66244a22336c7..18e6f2de1bc6f 100644
--- a/src/plugins/newsfeed/tsconfig.json
+++ b/src/plugins/newsfeed/tsconfig.json
@@ -7,13 +7,9 @@
"declaration": true,
"declarationMap": true
},
- "include": [
- "public/**/*",
- "server/**/*",
- "common/*",
- "../../../typings/**/*"
- ],
+ "include": ["public/**/*", "server/**/*", "common/*", "../../../typings/**/*"],
"references": [
- { "path": "../../core/tsconfig.json" }
+ { "path": "../../core/tsconfig.json" },
+ { "path": "../screenshot_mode/tsconfig.json" }
]
}
diff --git a/src/plugins/screenshot_mode/public/index.ts b/src/plugins/screenshot_mode/public/index.ts
index a5ad37dd5b760..012f57e837f41 100644
--- a/src/plugins/screenshot_mode/public/index.ts
+++ b/src/plugins/screenshot_mode/public/index.ts
@@ -18,4 +18,4 @@ export {
KBN_SCREENSHOT_MODE_ENABLED_KEY,
} from '../common';
-export { ScreenshotModePluginSetup } from './types';
+export { ScreenshotModePluginSetup, ScreenshotModePluginStart } from './types';
diff --git a/src/plugins/screenshot_mode/public/plugin.test.ts b/src/plugins/screenshot_mode/public/plugin.test.ts
index 33ae501466876..f2c0970d0ff60 100644
--- a/src/plugins/screenshot_mode/public/plugin.test.ts
+++ b/src/plugins/screenshot_mode/public/plugin.test.ts
@@ -21,7 +21,7 @@ describe('Screenshot mode public', () => {
setScreenshotModeDisabled();
});
- describe('setup contract', () => {
+ describe('public contract', () => {
it('detects screenshot mode "true"', () => {
setScreenshotModeEnabled();
const screenshotMode = plugin.setup(coreMock.createSetup());
@@ -34,10 +34,4 @@ describe('Screenshot mode public', () => {
expect(screenshotMode.isScreenshotMode()).toBe(false);
});
});
-
- describe('start contract', () => {
- it('returns nothing', () => {
- expect(plugin.start(coreMock.createStart())).toBe(undefined);
- });
- });
});
diff --git a/src/plugins/screenshot_mode/public/plugin.ts b/src/plugins/screenshot_mode/public/plugin.ts
index 7a166566a0173..a005bb7c3d055 100644
--- a/src/plugins/screenshot_mode/public/plugin.ts
+++ b/src/plugins/screenshot_mode/public/plugin.ts
@@ -8,18 +8,22 @@
import { CoreSetup, CoreStart, Plugin } from '../../../core/public';
-import { ScreenshotModePluginSetup } from './types';
+import { ScreenshotModePluginSetup, ScreenshotModePluginStart } from './types';
import { getScreenshotMode } from '../common';
export class ScreenshotModePlugin implements Plugin {
+ private publicContract = Object.freeze({
+ isScreenshotMode: () => getScreenshotMode() === true,
+ });
+
public setup(core: CoreSetup): ScreenshotModePluginSetup {
- return {
- isScreenshotMode: () => getScreenshotMode() === true,
- };
+ return this.publicContract;
}
- public start(core: CoreStart) {}
+ public start(core: CoreStart): ScreenshotModePluginStart {
+ return this.publicContract;
+ }
public stop() {}
}
diff --git a/src/plugins/screenshot_mode/public/types.ts b/src/plugins/screenshot_mode/public/types.ts
index 744ea8615f2a7..f6963de0cbd63 100644
--- a/src/plugins/screenshot_mode/public/types.ts
+++ b/src/plugins/screenshot_mode/public/types.ts
@@ -15,3 +15,4 @@ export interface IScreenshotModeService {
}
export type ScreenshotModePluginSetup = IScreenshotModeService;
+export type ScreenshotModePluginStart = IScreenshotModeService;
From 35714526c0758370e8a94c903a9468cb66b34961 Mon Sep 17 00:00:00 2001
From: Nathan Reese
Date: Tue, 15 Jun 2021 06:04:07 -0600
Subject: [PATCH 42/91] [Maps] Handle indices without geoip schema defined in
mapping (#100487)
* ensure sourceGeoField and destGeoField for docs included in pew pew source
* tslint
---
.../spatial_filter_utils.test.ts | 214 ++++++++++++++----
.../spatial_filter_utils.ts | 19 +-
.../es_geo_grid_source.test.ts | 47 +++-
.../es_pew_pew_source/es_pew_pew_source.js | 14 ++
x-pack/plugins/maps/server/mvt/get_tile.ts | 22 +-
5 files changed, 246 insertions(+), 70 deletions(-)
diff --git a/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.test.ts b/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.test.ts
index d828aca4a1a00..7aef32dfb4f8a 100644
--- a/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.test.ts
+++ b/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.test.ts
@@ -23,11 +23,31 @@ describe('createExtentFilter', () => {
minLat: 35,
minLon: -89,
};
- const filter = createExtentFilter(mapExtent, [geoFieldName]);
- expect(filter.geo_bounding_box).toEqual({
- location: {
- top_left: [-89, 39],
- bottom_right: [-83, 35],
+ expect(createExtentFilter(mapExtent, [geoFieldName])).toEqual({
+ meta: {
+ alias: null,
+ disabled: false,
+ key: 'location',
+ negate: false,
+ },
+ query: {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'location',
+ },
+ },
+ {
+ geo_bounding_box: {
+ location: {
+ top_left: [-89, 39],
+ bottom_right: [-83, 35],
+ },
+ },
+ },
+ ],
+ },
},
});
});
@@ -39,11 +59,31 @@ describe('createExtentFilter', () => {
minLat: -100,
minLon: -190,
};
- const filter = createExtentFilter(mapExtent, [geoFieldName]);
- expect(filter.geo_bounding_box).toEqual({
- location: {
- top_left: [-180, 89],
- bottom_right: [180, -89],
+ expect(createExtentFilter(mapExtent, [geoFieldName])).toEqual({
+ meta: {
+ alias: null,
+ disabled: false,
+ key: 'location',
+ negate: false,
+ },
+ query: {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'location',
+ },
+ },
+ {
+ geo_bounding_box: {
+ location: {
+ top_left: [-180, 89],
+ bottom_right: [180, -89],
+ },
+ },
+ },
+ ],
+ },
},
});
});
@@ -55,11 +95,31 @@ describe('createExtentFilter', () => {
minLat: 35,
minLon: 100,
};
- const filter = createExtentFilter(mapExtent, [geoFieldName]);
- expect(filter.geo_bounding_box).toEqual({
- location: {
- top_left: [100, 39],
- bottom_right: [-160, 35],
+ expect(createExtentFilter(mapExtent, [geoFieldName])).toEqual({
+ meta: {
+ alias: null,
+ disabled: false,
+ key: 'location',
+ negate: false,
+ },
+ query: {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'location',
+ },
+ },
+ {
+ geo_bounding_box: {
+ location: {
+ top_left: [100, 39],
+ bottom_right: [-160, 35],
+ },
+ },
+ },
+ ],
+ },
},
});
});
@@ -71,11 +131,31 @@ describe('createExtentFilter', () => {
minLat: 35,
minLon: -200,
};
- const filter = createExtentFilter(mapExtent, [geoFieldName]);
- expect(filter.geo_bounding_box).toEqual({
- location: {
- top_left: [160, 39],
- bottom_right: [-100, 35],
+ expect(createExtentFilter(mapExtent, [geoFieldName])).toEqual({
+ meta: {
+ alias: null,
+ disabled: false,
+ key: 'location',
+ negate: false,
+ },
+ query: {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'location',
+ },
+ },
+ {
+ geo_bounding_box: {
+ location: {
+ top_left: [160, 39],
+ bottom_right: [-100, 35],
+ },
+ },
+ },
+ ],
+ },
},
});
});
@@ -87,11 +167,31 @@ describe('createExtentFilter', () => {
minLat: 35,
minLon: -191,
};
- const filter = createExtentFilter(mapExtent, [geoFieldName]);
- expect(filter.geo_bounding_box).toEqual({
- location: {
- top_left: [-180, 39],
- bottom_right: [180, 35],
+ expect(createExtentFilter(mapExtent, [geoFieldName])).toEqual({
+ meta: {
+ alias: null,
+ disabled: false,
+ key: 'location',
+ negate: false,
+ },
+ query: {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'location',
+ },
+ },
+ {
+ geo_bounding_box: {
+ location: {
+ top_left: [-180, 39],
+ bottom_right: [180, 35],
+ },
+ },
+ },
+ ],
+ },
},
});
});
@@ -184,23 +284,36 @@ describe('createSpatialFilterWithGeometry', () => {
negate: false,
type: 'spatial_filter',
},
- geo_shape: {
- 'geo.coordinates': {
- relation: 'INTERSECTS',
- shape: {
- coordinates: [
- [
- [-101.21639, 48.1413],
- [-101.21639, 41.84905],
- [-90.95149, 41.84905],
- [-90.95149, 48.1413],
- [-101.21639, 48.1413],
- ],
- ],
- type: 'Polygon',
- },
+ query: {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'geo.coordinates',
+ },
+ },
+ {
+ geo_shape: {
+ 'geo.coordinates': {
+ relation: 'INTERSECTS',
+ shape: {
+ coordinates: [
+ [
+ [-101.21639, 48.1413],
+ [-101.21639, 41.84905],
+ [-90.95149, 41.84905],
+ [-90.95149, 48.1413],
+ [-101.21639, 48.1413],
+ ],
+ ],
+ type: 'Polygon',
+ },
+ },
+ ignore_unmapped: true,
+ },
+ },
+ ],
},
- ignore_unmapped: true,
},
});
});
@@ -318,9 +431,22 @@ describe('createDistanceFilterWithMeta', () => {
negate: false,
type: 'spatial_filter',
},
- geo_distance: {
- distance: '1000km',
- 'geo.coordinates': [120, 30],
+ query: {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'geo.coordinates',
+ },
+ },
+ {
+ geo_distance: {
+ distance: '1000km',
+ 'geo.coordinates': [120, 30],
+ },
+ },
+ ],
+ },
},
});
});
diff --git a/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.ts b/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.ts
index 70df9e9646f50..9a2b2c21136df 100644
--- a/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.ts
+++ b/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.ts
@@ -31,13 +31,23 @@ function createMultiGeoFieldFilter(
}
if (geoFieldNames.length === 1) {
- const geoFilter = createGeoFilter(geoFieldNames[0]);
return {
meta: {
...meta,
key: geoFieldNames[0],
},
- ...geoFilter,
+ query: {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: geoFieldNames[0],
+ },
+ },
+ createGeoFilter(geoFieldNames[0]),
+ ],
+ },
+ },
};
}
@@ -201,8 +211,9 @@ export function extractFeaturesFromFilters(filters: GeoFilter[]): Feature[] {
}
} else {
const geoFieldName = filter.meta.key;
- if (geoFieldName) {
- geometry = extractGeometryFromFilter(geoFieldName, filter);
+ const spatialClause = filter?.query?.bool?.must?.[1];
+ if (geoFieldName && spatialClause) {
+ geometry = extractGeometryFromFilter(geoFieldName, spatialClause);
}
}
diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts
index ad413419a289b..1043ed8778304 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts
+++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts
@@ -205,13 +205,31 @@ describe('ESGeoGridSource', () => {
expect(getProperty('query')).toEqual(undefined);
expect(getProperty('filter')).toEqual([
{
- geo_bounding_box: { bar: { bottom_right: [180, -82.67628], top_left: [-180, 82.67628] } },
meta: {
alias: null,
disabled: false,
key: 'bar',
negate: false,
},
+ query: {
+ bool: {
+ must: [
+ {
+ exists: {
+ field: 'bar',
+ },
+ },
+ {
+ geo_bounding_box: {
+ bar: {
+ top_left: [-180, 82.67628],
+ bottom_right: [180, -82.67628],
+ },
+ },
+ },
+ ],
+ },
+ },
},
]);
expect(getProperty('aggs')).toEqual({
@@ -262,20 +280,33 @@ describe('ESGeoGridSource', () => {
});
describe('ITiledSingleLayerVectorSource', () => {
+ const mvtGeogridSource = new ESGeoGridSource(
+ {
+ id: 'foobar',
+ indexPatternId: 'fooIp',
+ geoField: geoFieldName,
+ metrics: [],
+ resolution: GRID_RESOLUTION.SUPER_FINE,
+ type: SOURCE_TYPES.ES_GEO_GRID,
+ requestType: RENDER_AS.HEATMAP,
+ },
+ {}
+ );
+
it('getLayerName', () => {
- expect(geogridSource.getLayerName()).toBe('source_layer');
+ expect(mvtGeogridSource.getLayerName()).toBe('source_layer');
});
it('getMinZoom', () => {
- expect(geogridSource.getMinZoom()).toBe(0);
+ expect(mvtGeogridSource.getMinZoom()).toBe(0);
});
it('getMaxZoom', () => {
- expect(geogridSource.getMaxZoom()).toBe(24);
+ expect(mvtGeogridSource.getMaxZoom()).toBe(24);
});
it('getUrlTemplateWithMeta', async () => {
- const urlTemplateWithMeta = await geogridSource.getUrlTemplateWithMeta(
+ const urlTemplateWithMeta = await mvtGeogridSource.getUrlTemplateWithMeta(
vectorSourceRequestMeta
);
@@ -283,19 +314,19 @@ describe('ESGeoGridSource', () => {
expect(urlTemplateWithMeta.minSourceZoom).toBe(0);
expect(urlTemplateWithMeta.maxSourceZoom).toBe(24);
expect(urlTemplateWithMeta.urlTemplate).toEqual(
- "rootdir/api/maps/mvt/getGridTile/{z}/{x}/{y}.pbf?geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!((geo_bounding_box:(bar:(bottom_right:!(180,-82.67628),top_left:!(-180,82.67628))),meta:(alias:!n,disabled:!f,key:bar,negate:!f)))),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point"
+ "rootdir/api/maps/mvt/getGridTile/{z}/{x}/{y}.pbf?geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point"
);
});
it('should include searchSourceId in urlTemplateWithMeta', async () => {
- const urlTemplateWithMeta = await geogridSource.getUrlTemplateWithMeta({
+ const urlTemplateWithMeta = await mvtGeogridSource.getUrlTemplateWithMeta({
...vectorSourceRequestMeta,
searchSessionId: '1',
});
expect(
urlTemplateWithMeta.urlTemplate.startsWith(
- "rootdir/api/maps/mvt/getGridTile/{z}/{x}/{y}.pbf?geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!((geo_bounding_box:(bar:(bottom_right:!(180,-82.67628),top_left:!(-180,82.67628))),meta:(alias:!n,disabled:!f,key:bar,negate:!f)))),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point"
+ "rootdir/api/maps/mvt/getGridTile/{z}/{x}/{y}.pbf?geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point&searchSessionId=1"
)
).toBe(true);
diff --git a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js
index 7ed24b4805997..86dd63be4b67b 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js
+++ b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js
@@ -142,6 +142,20 @@ export class ESPewPewSource extends AbstractESAggSource {
},
});
+ // pewpew source is often used with security solution index-pattern
+ // Some underlying indices may not contain geo fields
+ // Filter out documents without geo fields to avoid shard failures for those indices
+ searchSource.setField('filter', [
+ ...searchSource.getField('filter'),
+ // destGeoField exists ensured by buffer filter
+ // so only need additional check for sourceGeoField
+ {
+ exists: {
+ field: this._descriptor.sourceGeoField,
+ },
+ },
+ ]);
+
const esResponse = await this._runEsQuery({
requestId: this.getId(),
requestName: layerName,
diff --git a/x-pack/plugins/maps/server/mvt/get_tile.ts b/x-pack/plugins/maps/server/mvt/get_tile.ts
index 776d316440a56..82d2162986503 100644
--- a/x-pack/plugins/maps/server/mvt/get_tile.ts
+++ b/x-pack/plugins/maps/server/mvt/get_tile.ts
@@ -24,6 +24,7 @@ import {
} from '../../common/constants';
import {
+ createExtentFilter,
convertRegularRespToGeoJson,
hitsToGeoJson,
isTotalHitsGreaterThan,
@@ -254,21 +255,14 @@ export async function getTile({
}
function getTileSpatialFilter(geometryFieldName: string, tileBounds: ESBounds): unknown {
- return {
- geo_shape: {
- [geometryFieldName]: {
- shape: {
- type: 'envelope',
- // upper left and lower right points of the shape to represent a bounding rectangle in the format [[minLon, maxLat], [maxLon, minLat]]
- coordinates: [
- [tileBounds.top_left.lon, tileBounds.top_left.lat],
- [tileBounds.bottom_right.lon, tileBounds.bottom_right.lat],
- ],
- },
- relation: 'INTERSECTS',
- },
- },
+ const tileExtent = {
+ minLon: tileBounds.top_left.lon,
+ minLat: tileBounds.bottom_right.lat,
+ maxLon: tileBounds.bottom_right.lon,
+ maxLat: tileBounds.top_left.lat,
};
+ const tileExtentFilter = createExtentFilter(tileExtent, [geometryFieldName]);
+ return tileExtentFilter.query;
}
function esBboxToGeoJsonPolygon(esBounds: ESBounds, tileBounds: ESBounds): Polygon {
From c47bf482618f7896571358c725a104c2583fa445 Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Tue, 15 Jun 2021 14:33:29 +0200
Subject: [PATCH 43/91] background session limitation docs (#102050)
---
docs/discover/search-sessions.asciidoc | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/docs/discover/search-sessions.asciidoc b/docs/discover/search-sessions.asciidoc
index fec1b8b26dd74..b503e8cfba3b4 100644
--- a/docs/discover/search-sessions.asciidoc
+++ b/docs/discover/search-sessions.asciidoc
@@ -68,3 +68,19 @@ behaves differently:
* Relative dates are converted to absolute dates.
* Panning and zooming is disabled for maps.
* Changing a filter, query, or drilldown starts a new search session, which can be slow.
+
+[float]
+==== Limitations
+
+Certain visualization features do not fully support background search sessions yet. If a dashboard using these features gets restored,
+all panels using unsupported features won't load immediately, but instead send out additional data requests which can take a while to complete.
+In this case a warning *Your search session is still running* will be shown.
+
+You can either wait for these additional requests to complete or come back to the dashboard later when all data requests have been finished.
+
+A panel on a dashboard can behave like this if one of the following features is used:
+* *Lens* - A *top values* dimension with an enabled setting *Group other values as "Other"* (configurable in the *Advanced* section of the dimension)
+* *Lens* - An *intervals* dimension is used
+* *Aggregation based* visualizations - A *terms* aggregation is used with an enabled setting *Group other values in separate bucket*
+* *Aggregation based* visualizations - A *histogram* aggregation is used
+* *Maps* - Layers using joins, blended layers or tracks layers are used
From b40732507e744d31d60b7af59516071fd56a969e Mon Sep 17 00:00:00 2001
From: Marco Liberati
Date: Tue, 15 Jun 2021 14:47:25 +0200
Subject: [PATCH 44/91] [Lens] Keep filters when transitioning from calculation
to regular operation (#101872)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../operations/layer_helpers.test.ts | 10 +++++++++-
.../operations/layer_helpers.ts | 12 +++++++++++-
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts
index ba3bee415f3f4..1ae2f4421a0bc 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts
@@ -1797,12 +1797,14 @@ describe('state_helpers', () => {
it('should promote the inner references when switching away from reference to field-based operation (case a2)', () => {
const expectedCol = {
- label: 'Count of records',
+ label: 'Count of records -3h',
dataType: 'number' as const,
isBucketed: false,
operationType: 'count' as const,
sourceField: 'Records',
+ filter: { language: 'kuery', query: 'bytes > 4000' },
+ timeShift: '3h',
};
const layer: IndexPatternLayer = {
indexPatternId: '1',
@@ -1817,6 +1819,8 @@ describe('state_helpers', () => {
// @ts-expect-error not a valid type
operationType: 'testReference',
references: ['col1'],
+ filter: { language: 'kuery', query: 'bytes > 4000' },
+ timeShift: '3h',
},
},
};
@@ -1845,6 +1849,8 @@ describe('state_helpers', () => {
isBucketed: false,
sourceField: 'bytes',
operationType: 'average' as const,
+ filter: { language: 'kuery', query: 'bytes > 4000' },
+ timeShift: '3h',
};
const layer: IndexPatternLayer = {
@@ -1858,6 +1864,8 @@ describe('state_helpers', () => {
isBucketed: false,
operationType: 'differences',
references: ['metric'],
+ filter: { language: 'kuery', query: 'bytes > 4000' },
+ timeShift: '3h',
},
},
};
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts
index b650a2818b2d4..4e3bcec4b6ca2 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts
@@ -414,11 +414,21 @@ export function replaceColumn({
indexPattern,
});
+ const column = copyCustomLabel({ ...referenceColumn }, previousColumn);
+ // do not forget to move over also any filter/shift/anything (if compatible)
+ // from the reference definition to the new operation.
+ if (referencedOperation.filterable) {
+ column.filter = (previousColumn as ReferenceBasedIndexPatternColumn).filter;
+ }
+ if (referencedOperation.shiftable) {
+ column.timeShift = (previousColumn as ReferenceBasedIndexPatternColumn).timeShift;
+ }
+
tempLayer = {
...tempLayer,
columns: {
...tempLayer.columns,
- [columnId]: copyCustomLabel({ ...referenceColumn }, previousColumn),
+ [columnId]: column,
},
};
return updateDefaultLabels(
From fb138cf165beb292e06525a3f8d6672daaa661fc Mon Sep 17 00:00:00 2001
From: SoNice! <62436227+d1v1b@users.noreply.github.com>
Date: Tue, 15 Jun 2021 21:50:01 +0900
Subject: [PATCH 45/91] [Fleet] Remove release messaging (#101450)
Co-authored-by: kobarei
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../fleet/public/applications/fleet/layouts/default.tsx | 3 +--
x-pack/plugins/translations/translations/ja-JP.json | 9 ---------
x-pack/plugins/translations/translations/zh-CN.json | 9 ---------
3 files changed, 1 insertion(+), 20 deletions(-)
diff --git a/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx
index 49836e9ed4ca6..d707fd162ae02 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx
@@ -18,7 +18,7 @@ import {
import { FormattedMessage } from '@kbn/i18n/react';
import type { Section } from '../sections';
-import { AlphaMessaging, SettingFlyout } from '../components';
+import { SettingFlyout } from '../components';
import { useLink, useConfig, useUrlModal } from '../hooks';
interface Props {
@@ -144,7 +144,6 @@ export const DefaultLayout: React.FunctionComponent = ({
) : null}
{children}
-
>
);
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 7409000156ae2..19390ad3d3703 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -8924,15 +8924,6 @@
"xpack.fleet.agentStatus.offlineLabel": "オフライン",
"xpack.fleet.agentStatus.unhealthyLabel": "異常",
"xpack.fleet.agentStatus.updatingLabel": "更新中",
- "xpack.fleet.alphaMessageDescription": "Fleet は本番環境用ではありません。",
- "xpack.fleet.alphaMessageLinkText": "詳細を参照してください。",
- "xpack.fleet.alphaMessageTitle": "ベータリリース",
- "xpack.fleet.alphaMessaging.docsLink": "ドキュメンテーション",
- "xpack.fleet.alphaMessaging.feedbackText": "{docsLink}をご覧ください。質問やフィードバックについては、{forumLink}にアクセスしてください。",
- "xpack.fleet.alphaMessaging.flyoutTitle": "このリリースについて",
- "xpack.fleet.alphaMessaging.forumLink": "ディスカッションフォーラム",
- "xpack.fleet.alphaMessaging.introText": "Fleet は開発中であり、本番環境用ではありません。このベータリリースは、ユーザーが Fleet と新しい Elastic エージェントをテストしてフィードバックを提供することを目的としています。このプラグインには、サポート SLA が適用されません。",
- "xpack.fleet.alphaMessging.closeFlyoutLabel": "閉じる",
"xpack.fleet.appNavigation.agentsLinkText": "エージェント",
"xpack.fleet.appNavigation.dataStreamsLinkText": "データストリーム",
"xpack.fleet.appNavigation.overviewLinkText": "概要",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 72a0d5f26b423..c19f070b0ea30 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -9004,15 +9004,6 @@
"xpack.fleet.agentStatus.offlineLabel": "脱机",
"xpack.fleet.agentStatus.unhealthyLabel": "运行不正常",
"xpack.fleet.agentStatus.updatingLabel": "正在更新",
- "xpack.fleet.alphaMessageDescription": "不推荐在生产环境中使用 Fleet。",
- "xpack.fleet.alphaMessageLinkText": "查看更多详情。",
- "xpack.fleet.alphaMessageTitle": "公测版",
- "xpack.fleet.alphaMessaging.docsLink": "文档",
- "xpack.fleet.alphaMessaging.feedbackText": "阅读我们的{docsLink}或前往我们的{forumLink},以了解问题或提供反馈。",
- "xpack.fleet.alphaMessaging.flyoutTitle": "关于本版本",
- "xpack.fleet.alphaMessaging.forumLink": "讨论论坛",
- "xpack.fleet.alphaMessaging.introText": "Fleet 仍处于开发状态,不适用于生产环境。此公测版用于用户测试 Fleet 和新 Elastic 代理并提供相关反馈。此插件不受支持 SLA 的约束。",
- "xpack.fleet.alphaMessging.closeFlyoutLabel": "关闭",
"xpack.fleet.appNavigation.agentsLinkText": "代理",
"xpack.fleet.appNavigation.dataStreamsLinkText": "数据流",
"xpack.fleet.appNavigation.overviewLinkText": "概览",
From ede8432ac7425f125b64ba96a99dacbdd02ef78d Mon Sep 17 00:00:00 2001
From: James Gowdy
Date: Tue, 15 Jun 2021 14:10:24 +0100
Subject: [PATCH 46/91] [ML] Improvements to job saved object sync (#101899)
* [ML] Improvements to job saved object sync
* refactor
* adding testsadding testsadding testsadding testsadding testsadding testsadding testsadding tests
* updating test label
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../plugins/ml/server/saved_objects/checks.ts | 6 +-
.../plugins/ml/server/saved_objects/sync.ts | 17 +++-
.../apis/ml/saved_objects/sync.ts | 93 ++++++++++++++++---
3 files changed, 99 insertions(+), 17 deletions(-)
diff --git a/x-pack/plugins/ml/server/saved_objects/checks.ts b/x-pack/plugins/ml/server/saved_objects/checks.ts
index 6b24ef000b695..48ceaefb23939 100644
--- a/x-pack/plugins/ml/server/saved_objects/checks.ts
+++ b/x-pack/plugins/ml/server/saved_objects/checks.ts
@@ -24,7 +24,7 @@ interface JobSavedObjectStatus {
};
}
-interface JobStatus {
+export interface JobStatus {
jobId: string;
datafeedId?: string | null;
checks: {
@@ -68,7 +68,9 @@ export function checksFactory(
if (type === 'anomaly-detector') {
jobExists = adJobs.jobs.some((j) => j.job_id === jobId);
- datafeedExists = datafeeds.datafeeds.some((d) => d.job_id === jobId);
+ datafeedExists = datafeeds.datafeeds.some(
+ (d) => d.datafeed_id === datafeedId && d.job_id === jobId
+ );
} else {
jobExists = dfaJobs.data_frame_analytics.some((j) => j.id === jobId);
}
diff --git a/x-pack/plugins/ml/server/saved_objects/sync.ts b/x-pack/plugins/ml/server/saved_objects/sync.ts
index a59687b9c3cbf..d6fa887d1d68b 100644
--- a/x-pack/plugins/ml/server/saved_objects/sync.ts
+++ b/x-pack/plugins/ml/server/saved_objects/sync.ts
@@ -14,6 +14,7 @@ import {
InitializeSavedObjectResponse,
} from '../../common/types/saved_objects';
import { checksFactory } from './checks';
+import type { JobStatus } from './checks';
import { getSavedObjectClientError } from './util';
import { Datafeed } from '../../common/types/anomaly_detection_jobs';
@@ -45,6 +46,12 @@ export function syncSavedObjectsFactory(
const tasks: Array<() => Promise> = [];
const status = await checkStatus();
+
+ const adJobsById = status.jobs['anomaly-detector'].reduce((acc, j) => {
+ acc[j.jobId] = j;
+ return acc;
+ }, {} as Record);
+
for (const job of status.jobs['anomaly-detector']) {
if (job.checks.savedObjectExits === false) {
if (simulate === true) {
@@ -141,8 +148,16 @@ export function syncSavedObjectsFactory(
}
for (const job of status.savedObjects['anomaly-detector']) {
- if (job.checks.datafeedExists === true && job.datafeedId === null) {
+ if (
+ (job.checks.datafeedExists === true && job.datafeedId === null) ||
+ (job.checks.datafeedExists === false &&
+ job.datafeedId === null &&
+ job.checks.datafeedExists === false &&
+ adJobsById[job.jobId] &&
+ adJobsById[job.jobId].datafeedId !== job.datafeedId)
+ ) {
// add datafeed id for jobs where the datafeed exists but the id is missing from the saved object
+ // or if the datafeed id in the saved object is not the same as the one attached to the job in es
if (simulate === true) {
results.datafeedsAdded[job.jobId] = { success: true };
} else {
diff --git a/x-pack/test/api_integration/apis/ml/saved_objects/sync.ts b/x-pack/test/api_integration/apis/ml/saved_objects/sync.ts
index e861dc528b237..e8c940d6b29b6 100644
--- a/x-pack/test/api_integration/apis/ml/saved_objects/sync.ts
+++ b/x-pack/test/api_integration/apis/ml/saved_objects/sync.ts
@@ -6,6 +6,7 @@
*/
import expect from '@kbn/expect';
+import { cloneDeep } from 'lodash';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { USER } from '../../../../functional/services/ml/security_common';
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';
@@ -23,7 +24,7 @@ export default ({ getService }: FtrProviderContext) => {
async function runRequest(user: USER, expectedStatusCode: number) {
const { body } = await supertest
- .get(`/s/space1/api/ml/saved_objects/sync`)
+ .get(`/s/${idSpace1}/api/ml/saved_objects/sync`)
.auth(user, ml.securityCommon.getPasswordForUser(user))
.set(COMMON_REQUEST_HEADERS)
.expect(expectedStatusCode);
@@ -32,9 +33,19 @@ export default ({ getService }: FtrProviderContext) => {
}
describe('GET saved_objects/sync', () => {
- before(async () => {
+ beforeEach(async () => {
await spacesService.create({ id: idSpace1, name: 'space_one', disabledFeatures: [] });
+ await ml.testResources.setKibanaTimeZoneToUTC();
+ });
+ afterEach(async () => {
+ await spacesService.delete(idSpace1);
+ await ml.api.cleanMlIndices();
+ await ml.testResources.cleanMLSavedObjects();
+ });
+
+ it('should sync datafeeds and saved objects', async () => {
+ // prepare test data
await ml.api.createAnomalyDetectionJob(
ml.commonConfig.getADFqSingleMetricJobConfig(adJobId1),
idSpace1
@@ -51,18 +62,6 @@ export default ({ getService }: FtrProviderContext) => {
ml.commonConfig.getADFqSingleMetricJobConfig(adJobIdES)
);
- await ml.testResources.setKibanaTimeZoneToUTC();
- });
-
- after(async () => {
- await spacesService.delete(idSpace1);
- await ml.api.cleanMlIndices();
- await ml.testResources.cleanMLSavedObjects();
- });
-
- it('should sync datafeeds and saved objects', async () => {
- // prepare test data
-
// datafeed should be added with the request
const datafeedConfig2 = ml.commonConfig.getADFqDatafeedConfig(adJobId2);
await ml.api.createDatafeedES(datafeedConfig2);
@@ -89,7 +88,73 @@ export default ({ getService }: FtrProviderContext) => {
});
});
+ it('should sync datafeeds after recreation in ES with different name', async () => {
+ // prepare test data
+ const jobConfig1 = ml.commonConfig.getADFqSingleMetricJobConfig(adJobId1);
+ await ml.api.createAnomalyDetectionJob(jobConfig1, idSpace1);
+
+ // datafeed should be added with the request
+ const datafeedConfig1 = ml.commonConfig.getADFqDatafeedConfig(adJobId1);
+ await ml.api.createDatafeedES(datafeedConfig1);
+
+ // run the sync request and verify the response
+ const body = await runRequest(USER.ML_POWERUSER_ALL_SPACES, 200);
+
+ // expect datafeed to be added
+ expect(body).to.eql({
+ datafeedsAdded: { [adJobId1]: { success: true } },
+ datafeedsRemoved: {},
+ savedObjectsCreated: {},
+ savedObjectsDeleted: {},
+ });
+
+ // delete the datafeed but do not sync
+ await ml.api.deleteDatafeedES(datafeedConfig1.datafeed_id);
+
+ // create a new datafeed with a different id
+ const datafeedConfig2 = cloneDeep(datafeedConfig1);
+ datafeedConfig2.datafeed_id = `different_${datafeedConfig2.datafeed_id}`;
+ await ml.api.createDatafeedES(datafeedConfig2);
+
+ const body2 = await runRequest(USER.ML_POWERUSER_ALL_SPACES, 200);
+
+ // previous datafeed should be removed on first sync
+ expect(body2).to.eql({
+ datafeedsAdded: {},
+ datafeedsRemoved: { [adJobId1]: { success: true } },
+ savedObjectsCreated: {},
+ savedObjectsDeleted: {},
+ });
+
+ const body3 = await runRequest(USER.ML_POWERUSER_ALL_SPACES, 200);
+
+ // new datafeed will be added on second sync
+ expect(body3).to.eql({
+ datafeedsAdded: { [adJobId1]: { success: true } },
+ datafeedsRemoved: {},
+ savedObjectsCreated: {},
+ savedObjectsDeleted: {},
+ });
+ });
+
it('should not sync anything if all objects are already synced', async () => {
+ await ml.api.createAnomalyDetectionJob(
+ ml.commonConfig.getADFqSingleMetricJobConfig(adJobId1),
+ idSpace1
+ );
+ await ml.api.createAnomalyDetectionJob(
+ ml.commonConfig.getADFqSingleMetricJobConfig(adJobId2),
+ idSpace1
+ );
+ await ml.api.createAnomalyDetectionJob(
+ ml.commonConfig.getADFqSingleMetricJobConfig(adJobId3),
+ idSpace1
+ );
+ await ml.api.createAnomalyDetectionJobES(
+ ml.commonConfig.getADFqSingleMetricJobConfig(adJobIdES)
+ );
+
+ await runRequest(USER.ML_POWERUSER_ALL_SPACES, 200);
const body = await runRequest(USER.ML_POWERUSER_ALL_SPACES, 200);
expect(body).to.eql({
From 341f965452a20c9bb7fe69e39578f0db1c0778ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20S=C3=A1nchez?=
Date: Tue, 15 Jun 2021 15:12:31 +0200
Subject: [PATCH 47/91] Fixes empty page when removing last element. Fixes
wrong event filters count number. Also adds unit tests (#102173)
---
.../paginated_content/paginated_content.test.tsx | 16 ++++++++++++++++
.../paginated_content/paginated_content.tsx | 7 +++++++
.../pages/event_filters/store/selector.ts | 7 +++++++
.../pages/event_filters/store/selectors.test.ts | 14 ++++++++++++++
.../view/event_filters_list_page.tsx | 4 +++-
5 files changed, 47 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.test.tsx b/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.test.tsx
index fc5c19e95fb77..d677a4a9fd662 100644
--- a/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.test.tsx
@@ -140,6 +140,22 @@ describe('when using PaginatedContent', () => {
});
});
+ it('should call onChange when page is empty', () => {
+ render({
+ pagination: {
+ pageIndex: 1,
+ pageSizeOptions: [5, 10, 20],
+ pageSize: 10,
+ totalItemCount: 10,
+ },
+ });
+ expect(onChangeHandler).toHaveBeenCalledWith({
+ pageIndex: 0,
+ pageSize: 10,
+ });
+ expect(onChangeHandler).toHaveBeenCalledTimes(1);
+ });
+
it('should ignore items, error, noItemsMessage when `children` is used', () => {
render({ children: {'children being used here'}
});
expect(renderResult.getByTestId('custom-content')).not.toBeNull();
diff --git a/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.tsx b/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.tsx
index 890b21624eaf6..a6b2683316efe 100644
--- a/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.tsx
@@ -16,6 +16,7 @@ import React, {
useCallback,
useMemo,
useState,
+ useEffect,
} from 'react';
import {
CommonProps,
@@ -152,6 +153,12 @@ export const PaginatedContent = memo(
[pagination?.pageSize, pagination?.totalItemCount]
);
+ useEffect(() => {
+ if (pageCount > 0 && pageCount < (pagination?.pageIndex || 0) + 1) {
+ onChange({ pageIndex: pageCount - 1, pageSize: pagination?.pageSize || 0 });
+ }
+ }, [pageCount, onChange, pagination]);
+
const handleItemsPerPageChange: EuiTablePaginationProps['onChangeItemsPerPage'] = useCallback(
(pageSize) => {
onChange({ pageSize, pageIndex: pagination?.pageIndex || 0 });
diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts
index d4e81fd812668..fef6ccb99a17a 100644
--- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts
@@ -59,6 +59,13 @@ export const getListItems: EventFiltersSelector<
return apiResponseData?.data || [];
});
+export const getTotalCountListItems: EventFiltersSelector> = createSelector(
+ getListApiSuccessResponse,
+ (apiResponseData) => {
+ return apiResponseData?.total || 0;
+ }
+);
+
/**
* Will return the query that was used with the currently displayed list of content. If a new page
* of content is being loaded, this selector will then attempt to use the previousState to return
diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts
index ac2b16e51603c..9d2d3c394c416 100644
--- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts
@@ -17,6 +17,7 @@ import {
getCurrentListPageDataState,
getListApiSuccessResponse,
getListItems,
+ getTotalCountListItems,
getCurrentListItemsQuery,
getListPagination,
getListFetchError,
@@ -120,6 +121,19 @@ describe('event filters selectors', () => {
});
});
+ describe('getTotalCountListItems()', () => {
+ it('should return the list items from api response', () => {
+ setToLoadedState();
+ expect(getTotalCountListItems(initialState)).toEqual(
+ getLastLoadedResourceState(initialState.listPage.data)?.data.content.total
+ );
+ });
+
+ it('should return empty array if no api response', () => {
+ expect(getTotalCountListItems(initialState)).toEqual(0);
+ });
+ });
+
describe('getCurrentListItemsQuery()', () => {
it('should return empty object if Uninitialized', () => {
expect(getCurrentListItemsQuery(initialState)).toEqual({});
diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx
index 00ee80c5d7022..0975104f02297 100644
--- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx
@@ -32,6 +32,7 @@ import {
getActionError,
getFormEntry,
showDeleteModal,
+ getTotalCountListItems,
} from '../store/selector';
import { PaginatedContent, PaginatedContentProps } from '../../../components/paginated_content';
import { Immutable, ListPageRouteState } from '../../../../../common/endpoint/types';
@@ -66,6 +67,7 @@ export const EventFiltersListPage = memo(() => {
const isActionError = useEventFiltersSelector(getActionError);
const formEntry = useEventFiltersSelector(getFormEntry);
const listItems = useEventFiltersSelector(getListItems);
+ const totalCountListItems = useEventFiltersSelector(getTotalCountListItems);
const pagination = useEventFiltersSelector(getListPagination);
const isLoading = useEventFiltersSelector(getListIsLoading);
const fetchError = useEventFiltersSelector(getListFetchError);
@@ -235,7 +237,7 @@ export const EventFiltersListPage = memo(() => {
From 68d24080e0d50dd64b16b4de93ddff924234a91c Mon Sep 17 00:00:00 2001
From: Larry Gregory
Date: Tue, 15 Jun 2021 09:25:47 -0400
Subject: [PATCH 48/91] Optimize privilege response validation (#102135)
---
.../validate_es_response.test.ts.snap | 2 +-
.../authorization/validate_es_response.ts | 18 ++++++++++--------
2 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/x-pack/plugins/security/server/authorization/__snapshots__/validate_es_response.test.ts.snap b/x-pack/plugins/security/server/authorization/__snapshots__/validate_es_response.test.ts.snap
index 04190fbf5eacd..b1bc14cc79e64 100644
--- a/x-pack/plugins/security/server/authorization/__snapshots__/validate_es_response.test.ts.snap
+++ b/x-pack/plugins/security/server/authorization/__snapshots__/validate_es_response.test.ts.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`validateEsPrivilegeResponse fails validation when an action is malformed in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: expected value of type [boolean] but got [string]"`;
+exports[`validateEsPrivilegeResponse fails validation when an action is malformed in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: [2]: expected value of type [boolean] but got [string]"`;
exports[`validateEsPrivilegeResponse fails validation when an action is missing in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: Payload did not match expected actions"`;
diff --git a/x-pack/plugins/security/server/authorization/validate_es_response.ts b/x-pack/plugins/security/server/authorization/validate_es_response.ts
index d941b2d777c4f..223aee37a9047 100644
--- a/x-pack/plugins/security/server/authorization/validate_es_response.ts
+++ b/x-pack/plugins/security/server/authorization/validate_es_response.ts
@@ -9,6 +9,11 @@ import { schema } from '@kbn/config-schema';
import type { HasPrivilegesResponse } from './types';
+const anyBoolean = schema.boolean();
+const anyBooleanArray = schema.arrayOf(anyBoolean);
+const anyString = schema.string();
+const anyObject = schema.object({}, { unknowns: 'allow' });
+
/**
* Validates an Elasticsearch "Has privileges" response against the expected application, actions, and resources.
*
@@ -31,7 +36,6 @@ export function validateEsPrivilegeResponse(
}
function buildValidationSchema(application: string, actions: string[], resources: string[]) {
- const actionValidationSchema = schema.boolean();
const actionsValidationSchema = schema.object(
{},
{
@@ -45,9 +49,7 @@ function buildValidationSchema(application: string, actions: string[], resources
throw new Error('Payload did not match expected actions');
}
- Object.values(value).forEach((actionResult) => {
- actionValidationSchema.validate(actionResult);
- });
+ anyBooleanArray.validate(Object.values(value));
},
}
);
@@ -73,12 +75,12 @@ function buildValidationSchema(application: string, actions: string[], resources
);
return schema.object({
- username: schema.string(),
- has_all_requested: schema.boolean(),
- cluster: schema.object({}, { unknowns: 'allow' }),
+ username: anyString,
+ has_all_requested: anyBoolean,
+ cluster: anyObject,
application: schema.object({
[application]: resourcesValidationSchema,
}),
- index: schema.object({}, { unknowns: 'allow' }),
+ index: anyObject,
});
}
From a7709b48b5195feabaa68a3b4e322be46501ff8f Mon Sep 17 00:00:00 2001
From: Ignacio Rivas
Date: Tue, 15 Jun 2021 15:27:48 +0200
Subject: [PATCH 49/91] [Ingest Node Pipelines] Migrate to new page layout
(#101894)
* migrate pages to new layout
* fix linter errors
* update translation files
* Nicer try-again cta
* Fix lang and prettier
* small CR changes
* small linter fixes
* fix test copy
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../ingest_pipelines_list.test.ts | 2 +-
.../public/application/app.tsx | 54 +++---
.../pipelines_clone/pipelines_clone.tsx | 15 +-
.../pipelines_create/pipelines_create.tsx | 92 ++++-----
.../pipelines_edit/pipelines_edit.tsx | 158 ++++++++--------
.../sections/pipelines_list/empty_list.tsx | 79 ++++----
.../sections/pipelines_list/main.tsx | 175 ++++++++----------
.../translations/translations/ja-JP.json | 3 -
.../translations/translations/zh-CN.json | 3 -
9 files changed, 280 insertions(+), 301 deletions(-)
diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts
index 76ebe21a51367..78e3f2dab0d1d 100644
--- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts
+++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_list.test.ts
@@ -162,7 +162,7 @@ describe(' ', () => {
const { exists, find } = testBed;
expect(exists('pipelineLoadError')).toBe(true);
- expect(find('pipelineLoadError').text()).toContain('Unable to load pipelines.');
+ expect(find('pipelineLoadError').text()).toContain('Unable to load pipelines');
});
});
});
diff --git a/x-pack/plugins/ingest_pipelines/public/application/app.tsx b/x-pack/plugins/ingest_pipelines/public/application/app.tsx
index 1cca7a0721fbc..da8f74e1efae5 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/app.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/app.tsx
@@ -6,7 +6,7 @@
*/
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiPageContent } from '@elastic/eui';
+import { EuiPageContent, EuiEmptyPrompt } from '@elastic/eui';
import React, { FunctionComponent } from 'react';
import { Router, Switch, Route } from 'react-router-dom';
@@ -19,7 +19,6 @@ import {
useAuthorizationContext,
WithPrivileges,
SectionLoading,
- NotAuthorizedSection,
} from '../shared_imports';
import { PipelinesList, PipelinesCreate, PipelinesEdit, PipelinesClone } from './sections';
@@ -61,35 +60,42 @@ export const App: FunctionComponent = () => {
{({ isLoading, hasPrivileges, privilegesMissing }) => {
if (isLoading) {
return (
-
-
-
+
+
+
+
+
);
}
if (!hasPrivileges) {
return (
-
-
+
+
+
+
}
- message={
-
+ body={
+
+
+
}
/>
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx
index c51ed94cbc116..f68b64cc5f613 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx
@@ -8,6 +8,7 @@
import React, { FunctionComponent, useEffect } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
+import { EuiPageContent } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { SectionLoading, useKibana, attemptToURIDecode } from '../../../shared_imports';
@@ -45,12 +46,14 @@ export const PipelinesClone: FunctionComponent>
if (isLoading && isInitialRequest) {
return (
-
-
-
+
+
+
+
+
);
} else {
// We still show the create form even if we were not able to load the
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx
index eefc74e2dc6fa..5aa9205e1e1e5 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx
@@ -8,15 +8,7 @@
import React, { useState, useEffect } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react';
-import {
- EuiPageBody,
- EuiPageContent,
- EuiTitle,
- EuiFlexGroup,
- EuiFlexItem,
- EuiButtonEmpty,
- EuiSpacer,
-} from '@elastic/eui';
+import { EuiPageHeader, EuiButtonEmpty, EuiSpacer } from '@elastic/eui';
import { getListPath } from '../../services/navigation';
import { Pipeline } from '../../../../common/types';
@@ -64,49 +56,43 @@ export const PipelinesCreate: React.FunctionComponent
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ <>
+
+
+
+ }
+ rightSideItems={[
+
+
+ ,
+ ]}
+ />
+
+
+
+
+ >
);
};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx
index 6011a36d292fa..ea47f4c9a25e9 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx
@@ -9,16 +9,14 @@ import React, { useState, useEffect } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react';
import {
- EuiPageBody,
+ EuiPageHeader,
+ EuiEmptyPrompt,
EuiPageContent,
EuiSpacer,
- EuiTitle,
- EuiFlexGroup,
- EuiFlexItem,
+ EuiButton,
EuiButtonEmpty,
} from '@elastic/eui';
-import { EuiCallOut } from '@elastic/eui';
import { Pipeline } from '../../../../common/types';
import { useKibana, SectionLoading, attemptToURIDecode } from '../../../shared_imports';
@@ -42,7 +40,9 @@ export const PipelinesEdit: React.FunctionComponent {
setIsSaving(true);
@@ -68,88 +68,92 @@ export const PipelinesEdit: React.FunctionComponent
-
-
+ return (
+
+
+
+
+
);
- } else if (error) {
- content = (
- <>
-
+
+
+
+
}
- color="danger"
- iconType="alert"
- data-test-subj="fetchPipelineError"
- >
- {error.message}
-
-
- >
+ body={{error.message}
}
+ actions={
+
+
+
+ }
+ />
+
);
- } else if (pipeline) {
- content = (
+ }
+
+ return (
+ <>
+
+
+
+ }
+ rightSideItems={[
+
+
+ ,
+ ]}
+ />
+
+
+
- );
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {content}
-
-
+ >
);
};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx
index 7d69bd3fb8cf3..9f401bca5431f 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx
@@ -8,7 +8,7 @@
import React, { FunctionComponent } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiEmptyPrompt, EuiLink, EuiPageBody, EuiPageContent, EuiButton } from '@elastic/eui';
+import { EuiEmptyPrompt, EuiLink, EuiPageContent, EuiButton } from '@elastic/eui';
import { useHistory } from 'react-router-dom';
import { ScopedHistory } from 'kibana/public';
import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public';
@@ -20,46 +20,43 @@ export const EmptyList: FunctionComponent = () => {
const history = useHistory() as ScopedHistory;
return (
-
-
-
- {i18n.translate('xpack.ingestPipelines.list.table.emptyPromptTitle', {
- defaultMessage: 'Start by creating a pipeline',
+
+
+ {i18n.translate('xpack.ingestPipelines.list.table.emptyPromptTitle', {
+ defaultMessage: 'Start by creating a pipeline',
+ })}
+
+ }
+ body={
+
+ {' '}
+
+ {i18n.translate('xpack.ingestPipelines.list.table.emptyPromptDocumentionLink', {
+ defaultMessage: 'Learn more',
})}
-
- }
- body={
-
-
-
-
- {i18n.translate('xpack.ingestPipelines.list.table.emptyPromptDocumentionLink', {
- defaultMessage: 'Learn more',
- })}
-
-
- }
- actions={
-
- {i18n.translate('xpack.ingestPipelines.list.table.emptyPrompt.createButtonLabel', {
- defaultMessage: 'Create a pipeline',
- })}
-
- }
- />
-
-
+
+
+ }
+ actions={
+
+ {i18n.translate('xpack.ingestPipelines.list.table.emptyPrompt.createButtonLabel', {
+ defaultMessage: 'Create a pipeline',
+ })}
+
+ }
+ />
+
);
};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx
index 454747fe0870e..ae68cfcb399f0 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx
@@ -12,16 +12,12 @@ import { Location } from 'history';
import { parse } from 'query-string';
import {
- EuiPageBody,
- EuiPageContent,
- EuiTitle,
- EuiFlexGroup,
- EuiFlexItem,
+ EuiPageHeader,
EuiButtonEmpty,
- EuiCallOut,
- EuiLink,
+ EuiPageContent,
+ EuiEmptyPrompt,
+ EuiButton,
EuiSpacer,
- EuiText,
} from '@elastic/eui';
import { Pipeline } from '../../../../common/types';
@@ -81,33 +77,50 @@ export const PipelinesList: React.FunctionComponent = ({
history.push(getListPath());
};
- if (data && data.length === 0) {
- return ;
+ if (error) {
+ return (
+
+
+
+
+ }
+ body={{error.message}
}
+ actions={
+
+
+
+ }
+ />
+
+ );
}
- let content: React.ReactNode;
-
if (isLoading) {
- content = (
-
-
-
- );
- } else if (data?.length) {
- content = (
-
+ return (
+
+
+
+
+
);
}
+ if (data && data.length === 0) {
+ return ;
+ }
+
const renderFlyout = (): React.ReactNode => {
if (!showFlyout) {
return;
@@ -134,71 +147,47 @@ export const PipelinesList: React.FunctionComponent = ({
return (
<>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Error call out for pipeline table */}
- {error ? (
-
-
-
- ),
- }}
- />
- }
+
+
- ) : (
- content
- )}
-
-
+
+ }
+ description={
+
+ }
+ rightSideItems={[
+
+
+ ,
+ ]}
+ />
+
+
+
+
+
{renderFlyout()}
{pipelinesToDelete?.length > 0 ? (
Date: Tue, 15 Jun 2021 15:28:40 +0200
Subject: [PATCH 50/91] [Remote Clusters] Migrate to new page layout (#102042)
* change page templates
* Small copy tweaks
* Update docs
* small CR changes
* exit function call if fails
* update i18n strings
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../list/remote_clusters_list.test.js | 4 +
.../remote_cluster_page_title.js | 76 ++----
.../remote_cluster_add/remote_cluster_add.js | 10 +-
.../remote_cluster_edit.js | 161 ++++++------
.../remote_cluster_list.js | 236 +++++++++---------
.../remote_cluster_table.js | 13 +
.../store/actions/load_clusters.js | 13 +-
.../remote_clusters/public/shared_imports.ts | 6 +-
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
10 files changed, 245 insertions(+), 276 deletions(-)
diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js
index c91732019f79f..209c224618f78 100644
--- a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js
+++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js
@@ -181,6 +181,10 @@ describe(' ', () => {
expect(exists('remoteClusterCreateButton')).toBe(true);
});
+ test('should have link to documentation', () => {
+ expect(exists('documentationLink')).toBe(true);
+ });
+
test('should list the remote clusters in the table', () => {
expect(tableCellsValues.length).toEqual(remoteClusters.length);
expect(tableCellsValues).toEqual([
diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_page_title/remote_cluster_page_title.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_page_title/remote_cluster_page_title.js
index 1706be8cfbe2f..2b2e7338fb6b0 100644
--- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_page_title/remote_cluster_page_title.js
+++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_page_title/remote_cluster_page_title.js
@@ -5,62 +5,38 @@
* 2.0.
*/
-import React, { Fragment } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@kbn/i18n/react';
import { remoteClustersUrl } from '../../../services/documentation';
-import {
- EuiButtonEmpty,
- EuiFlexGroup,
- EuiFlexItem,
- EuiSpacer,
- EuiText,
- EuiTitle,
-} from '@elastic/eui';
+import { EuiPageHeader, EuiButtonEmpty, EuiSpacer } from '@elastic/eui';
export const RemoteClusterPageTitle = ({ title, description }) => (
-
-
-
-
-
-
-
- {title}
-
-
-
-
-
-
-
-
-
-
-
- {description ? (
- <>
-
-
-
- {description}
-
- >
- ) : null}
-
-
-
+ <>
+ {title}}
+ rightSideItems={[
+
+
+ ,
+ ]}
+ description={description}
+ />
+
+
+ >
);
RemoteClusterPageTitle.propTypes = {
diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js
index 124d2d42afb78..f62550ca5aa10 100644
--- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js
+++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js
@@ -9,8 +9,6 @@ import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiPageContent } from '@elastic/eui';
-
import { extractQueryParams } from '../../../shared_imports';
import { getRouter, redirect } from '../../services';
import { setBreadcrumbs } from '../../services/breadcrumb';
@@ -58,11 +56,7 @@ export class RemoteClusterAdd extends PureComponent {
const { isAddingCluster, addClusterError } = this.props;
return (
-
+ <>
-
+ >
);
}
}
diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js
index 18ee2e2b3875d..1f3388d06e54c 100644
--- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js
+++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js
@@ -5,27 +5,17 @@
* 2.0.
*/
-import React, { Component, Fragment } from 'react';
+import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@kbn/i18n/react';
-import {
- EuiButtonEmpty,
- EuiCallOut,
- EuiFlexGroup,
- EuiFlexItem,
- EuiLoadingSpinner,
- EuiPageContent,
- EuiSpacer,
- EuiText,
- EuiTextColor,
-} from '@elastic/eui';
+import { EuiButton, EuiCallOut, EuiEmptyPrompt, EuiPageContent, EuiSpacer } from '@elastic/eui';
import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public';
-import { extractQueryParams } from '../../../shared_imports';
+import { extractQueryParams, SectionLoading } from '../../../shared_imports';
import { getRouter, redirect } from '../../services';
import { setBreadcrumbs } from '../../services/breadcrumb';
-import { RemoteClusterPageTitle, RemoteClusterForm, ConfiguredByNodeWarning } from '../components';
+import { RemoteClusterPageTitle, RemoteClusterForm } from '../components';
export class RemoteClusterEdit extends Component {
static propTypes = {
@@ -92,56 +82,50 @@ export class RemoteClusterEdit extends Component {
}
};
- renderContent() {
+ render() {
const { clusterName } = this.state;
const { isLoading, cluster, isEditingCluster, getEditClusterError } = this.props;
if (isLoading) {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
);
}
if (!cluster) {
return (
-
-
+
+
+
+
}
- color="danger"
- iconType="alert"
- >
-
-
-
-
-
-
+
+
+ }
+ actions={
+
@@ -149,10 +133,10 @@ export class RemoteClusterEdit extends Component {
id="xpack.remoteClusters.edit.viewRemoteClustersButtonLabel"
defaultMessage="View remote clusters"
/>
-
-
-
-
+
+ }
+ />
+
);
}
@@ -160,23 +144,50 @@ export class RemoteClusterEdit extends Component {
if (isConfiguredByNode) {
return (
-
-
-
-
-
-
-
-
-
+
+
+
+
+ }
+ body={
+
+
+
+ }
+ actions={
+
+
+
+ }
+ />
+
);
}
return (
<>
+
+ }
+ />
+
{hasDeprecatedProxySetting ? (
<>
>
) : null}
+
);
}
-
- render() {
- return (
-
-
- }
- />
-
- {this.renderContent()}
-
- );
- }
}
diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js
index ccf4c7568f7ad..b94ae8f7edbc0 100644
--- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js
+++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js
@@ -5,32 +5,24 @@
* 2.0.
*/
-import React, { Component, Fragment } from 'react';
+import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiButton,
+ EuiButtonEmpty,
EuiEmptyPrompt,
- EuiFlexGroup,
- EuiFlexItem,
EuiLoadingKibana,
- EuiLoadingSpinner,
EuiOverlayMask,
- EuiPageBody,
EuiPageContent,
- EuiPageContentHeader,
- EuiPageContentHeaderSection,
EuiSpacer,
- EuiText,
- EuiTextColor,
- EuiTitle,
- EuiCallOut,
+ EuiPageHeader,
} from '@elastic/eui';
+import { remoteClustersUrl } from '../../services/documentation';
import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public';
-import { extractQueryParams } from '../../../shared_imports';
+import { extractQueryParams, SectionLoading } from '../../../shared_imports';
import { setBreadcrumbs } from '../../services/breadcrumb';
import { RemoteClusterTable } from './remote_cluster_table';
@@ -82,41 +74,6 @@ export class RemoteClusterList extends Component {
clearInterval(this.interval);
}
- getHeaderSection(isAuthorized) {
- return (
-
-
-
-
-
-
-
-
-
-
- {isAuthorized && (
-
-
-
-
-
- )}
-
-
-
- );
- }
-
renderBlockingAction() {
const { isCopyingCluster, isRemovingCluster } = this.props;
@@ -132,16 +89,28 @@ export class RemoteClusterList extends Component {
}
renderNoPermission() {
- const title = i18n.translate('xpack.remoteClusters.remoteClusterList.noPermissionTitle', {
- defaultMessage: 'Permission error',
- });
return (
-
-
+
+
+
+ }
+ body={
+
+
+
+ }
/>
-
+
);
}
@@ -150,80 +119,84 @@ export class RemoteClusterList extends Component {
// handle unexpected error shapes in the API action.
const { statusCode, error: errorString } = error.body;
- const title = i18n.translate('xpack.remoteClusters.remoteClusterList.loadingErrorTitle', {
- defaultMessage: 'Error loading remote clusters',
- });
return (
-
- {statusCode} {errorString}
-
+
+
+
+
+ }
+ body={
+
+ {statusCode} {errorString}
+
+ }
+ />
+
);
}
renderEmpty() {
return (
-
-
-
- }
- body={
-
+
+
+
+
+ }
+ body={
-
- }
- actions={
-
-
-
- }
- />
+ }
+ actions={
+
+
+
+ }
+ />
+
);
}
renderLoading() {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
);
}
@@ -231,10 +204,35 @@ export class RemoteClusterList extends Component {
const { clusters } = this.props;
return (
-
+ <>
+
+ }
+ rightSideItems={[
+
+
+ ,
+ ]}
+ />
+
+
+
-
+ >
);
}
@@ -242,7 +240,6 @@ export class RemoteClusterList extends Component {
const { isLoading, clusters, clusterLoadError } = this.props;
const isEmpty = !isLoading && !clusters.length;
const isAuthorized = !clusterLoadError || clusterLoadError.status !== 403;
- const isHeaderVisible = clusterLoadError || !isEmpty;
let content;
@@ -261,13 +258,10 @@ export class RemoteClusterList extends Component {
}
return (
-
-
- {isHeaderVisible && this.getHeaderSection(isAuthorized)}
- {content}
- {this.renderBlockingAction()}
-
-
+ <>
+ {content}
+ {this.renderBlockingAction()}
+ >
);
}
}
diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js
index 3da8bb505fc54..1404e51d98a6d 100644
--- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js
+++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js
@@ -330,6 +330,19 @@ export class RemoteClusterTable extends Component {
)}
) : undefined,
+ toolsRight: (
+
+
+
+ ),
onChange: this.onSearch,
box: {
incremental: true,
diff --git a/x-pack/plugins/remote_clusters/public/application/store/actions/load_clusters.js b/x-pack/plugins/remote_clusters/public/application/store/actions/load_clusters.js
index 343237e70a120..1a6459627c9a1 100644
--- a/x-pack/plugins/remote_clusters/public/application/store/actions/load_clusters.js
+++ b/x-pack/plugins/remote_clusters/public/application/store/actions/load_clusters.js
@@ -5,9 +5,7 @@
* 2.0.
*/
-import { i18n } from '@kbn/i18n';
-
-import { loadClusters as sendLoadClustersRequest, showApiError } from '../../services';
+import { loadClusters as sendLoadClustersRequest } from '../../services';
import { LOAD_CLUSTERS_START, LOAD_CLUSTERS_SUCCESS, LOAD_CLUSTERS_FAILURE } from '../action_types';
@@ -20,17 +18,10 @@ export const loadClusters = () => async (dispatch) => {
try {
clusters = await sendLoadClustersRequest();
} catch (error) {
- dispatch({
+ return dispatch({
type: LOAD_CLUSTERS_FAILURE,
payload: { error },
});
-
- return showApiError(
- error,
- i18n.translate('xpack.remoteClusters.loadAction.errorTitle', {
- defaultMessage: 'Error loading remote clusters',
- })
- );
}
dispatch({
diff --git a/x-pack/plugins/remote_clusters/public/shared_imports.ts b/x-pack/plugins/remote_clusters/public/shared_imports.ts
index fd28175318666..c8d7f1d9f13f3 100644
--- a/x-pack/plugins/remote_clusters/public/shared_imports.ts
+++ b/x-pack/plugins/remote_clusters/public/shared_imports.ts
@@ -5,4 +5,8 @@
* 2.0.
*/
-export { extractQueryParams, indices } from '../../../../src/plugins/es_ui_shared/public';
+export {
+ extractQueryParams,
+ indices,
+ SectionLoading,
+} from '../../../../src/plugins/es_ui_shared/public';
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index a5d7829429f7b..b175b627ee1f0 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -17658,7 +17658,6 @@
"xpack.remoteClusters.form.errors.serverNameMissing": "サーバー名が必要です。",
"xpack.remoteClusters.licenseCheckErrorMessage": "ライセンス確認失敗",
"xpack.remoteClusters.listBreadcrumbTitle": "リモートクラスター",
- "xpack.remoteClusters.loadAction.errorTitle": "リモートクラスターの読み込み中にエラーが発生",
"xpack.remoteClusters.readDocsButtonLabel": "リモートクラスタードキュメント",
"xpack.remoteClusters.refreshAction.errorTitle": "リモートクラスターの更新中にエラーが発生",
"xpack.remoteClusters.remoteClusterForm.actions.savingText": "保存中",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index c1bc2c9cc0104..438c70d839806 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -17897,7 +17897,6 @@
"xpack.remoteClusters.form.errors.serverNameMissing": "服务器名必填。",
"xpack.remoteClusters.licenseCheckErrorMessage": "许可证检查失败",
"xpack.remoteClusters.listBreadcrumbTitle": "远程集群",
- "xpack.remoteClusters.loadAction.errorTitle": "加载远程集群时出错",
"xpack.remoteClusters.readDocsButtonLabel": "远程集群文档",
"xpack.remoteClusters.refreshAction.errorTitle": "刷新远程集群时出错",
"xpack.remoteClusters.remoteClusterForm.actions.savingText": "正在保存",
From 4b3adfda5f5bb78c205c3d4d8a335b649c462a5e Mon Sep 17 00:00:00 2001
From: "Joey F. Poon"
Date: Tue, 15 Jun 2021 08:57:12 -0500
Subject: [PATCH 51/91] [Security Solution] restyle endpoint details flyout
(#102092)
---
.../details/components/actions_menu.test.tsx | 2 +
.../view/details/endpoint_details.tsx | 167 +++++-------------
.../endpoint_hosts/view/details/index.tsx | 6 +-
.../view/hooks/use_endpoint_action_items.tsx | 24 +++
.../pages/endpoint_hosts/view/index.test.tsx | 36 +---
.../translations/translations/ja-JP.json | 2 -
.../translations/translations/zh-CN.json | 2 -
.../apps/endpoint/endpoint_list.ts | 3 +-
8 files changed, 79 insertions(+), 163 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx
index 7ecbad54dbbec..356d44a810528 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx
@@ -70,6 +70,7 @@ describe('When using the Endpoint Details Actions Menu', () => {
['View host details', 'hostLink'],
['View agent policy', 'agentPolicyLink'],
['View agent details', 'agentDetailsLink'],
+ ['Reassign agent policy', 'agentPolicyReassignLink'],
])('should display %s action', async (_, dataTestSubj) => {
await render();
expect(renderResult.getByTestId(dataTestSubj)).not.toBeNull();
@@ -80,6 +81,7 @@ describe('When using the Endpoint Details Actions Menu', () => {
['View host details', 'hostLink'],
['View agent policy', 'agentPolicyLink'],
['View agent details', 'agentDetailsLink'],
+ ['Reassign agent policy', 'agentPolicyReassignLink'],
])(
'should navigate via kibana `navigateToApp()` when %s is clicked',
async (_, dataTestSubj) => {
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx
index 16cae79d42c0f..38404a5c6c11f 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx
@@ -8,10 +8,8 @@
import styled from 'styled-components';
import {
EuiDescriptionList,
- EuiHorizontalRule,
EuiListGroup,
EuiListGroupItem,
- EuiIcon,
EuiText,
EuiFlexGroup,
EuiFlexItem,
@@ -23,17 +21,14 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { isPolicyOutOfDate } from '../../utils';
import { HostInfo, HostMetadata, HostStatus } from '../../../../../../common/endpoint/types';
-import { useEndpointSelector, useAgentDetailsIngestUrl } from '../hooks';
-import { useNavigateToAppEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
+import { useEndpointSelector } from '../hooks';
import { policyResponseStatus, uiQueryParams } from '../../store/selectors';
import { POLICY_STATUS_TO_BADGE_COLOR, HOST_STATUS_TO_BADGE_COLOR } from '../host_constants';
import { FormattedDateAndTime } from '../../../../../common/components/endpoint/formatted_date_time';
import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
-import { LinkToApp } from '../../../../../common/components/endpoint/link_to_app';
import { getEndpointDetailsPath } from '../../../../common/routing';
import { SecurityPageName } from '../../../../../app/types';
import { useFormatUrl } from '../../../../../common/components/link_to';
-import { AgentDetailsReassignPolicyAction } from '../../../../../../../fleet/public';
import { EndpointPolicyLink } from '../components/endpoint_policy_link';
import { OutOfDate } from '../components/out_of_date';
@@ -44,8 +39,6 @@ const HostIds = styled(EuiListGroupItem)`
}
`;
-const openReassignFlyoutSearch = '?openReassignFlyout=true';
-
export const EndpointDetails = memo(
({
details,
@@ -56,19 +49,34 @@ export const EndpointDetails = memo(
policyInfo?: HostInfo['policy_info'];
hostStatus: HostStatus;
}) => {
- const agentId = details.elastic.agent.id;
- const {
- url: agentDetailsUrl,
- appId: ingestAppId,
- appPath: agentDetailsAppPath,
- } = useAgentDetailsIngestUrl(agentId);
const queryParams = useEndpointSelector(uiQueryParams);
const policyStatus = useEndpointSelector(
policyResponseStatus
) as keyof typeof POLICY_STATUS_TO_BADGE_COLOR;
const { formatUrl } = useFormatUrl(SecurityPageName.administration);
- const detailsResultsUpper = useMemo(() => {
+ const [policyResponseUri, policyResponseRoutePath] = useMemo(() => {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ const { selected_endpoint, show, ...currentUrlParams } = queryParams;
+ return [
+ formatUrl(
+ getEndpointDetailsPath({
+ name: 'endpointPolicyResponse',
+ ...currentUrlParams,
+ selected_endpoint: details.agent.id,
+ })
+ ),
+ getEndpointDetailsPath({
+ name: 'endpointPolicyResponse',
+ ...currentUrlParams,
+ selected_endpoint: details.agent.id,
+ }),
+ ];
+ }, [details.agent.id, formatUrl, queryParams]);
+
+ const policyStatusClickHandler = useNavigateByRouterEventHandler(policyResponseRoutePath);
+
+ const detailsResults = useMemo(() => {
return [
{
title: i18n.translate('xpack.securitySolution.endpoint.details.os', {
@@ -106,55 +114,9 @@ export const EndpointDetails = memo(
),
},
- ];
- }, [details, hostStatus]);
-
- const [policyResponseUri, policyResponseRoutePath] = useMemo(() => {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- const { selected_endpoint, show, ...currentUrlParams } = queryParams;
- return [
- formatUrl(
- getEndpointDetailsPath({
- name: 'endpointPolicyResponse',
- ...currentUrlParams,
- selected_endpoint: details.agent.id,
- })
- ),
- getEndpointDetailsPath({
- name: 'endpointPolicyResponse',
- ...currentUrlParams,
- selected_endpoint: details.agent.id,
- }),
- ];
- }, [details.agent.id, formatUrl, queryParams]);
-
- const agentDetailsWithFlyoutPath = `${agentDetailsAppPath}${openReassignFlyoutSearch}`;
- const agentDetailsWithFlyoutUrl = `${agentDetailsUrl}${openReassignFlyoutSearch}`;
- const handleReassignEndpointsClick = useNavigateToAppEventHandler(
- ingestAppId,
- {
- path: agentDetailsWithFlyoutPath,
- state: {
- onDoneNavigateTo: [
- 'securitySolution:administration',
- {
- path: getEndpointDetailsPath({
- name: 'endpointDetails',
- selected_endpoint: details.agent.id,
- }),
- },
- ],
- },
- }
- );
-
- const policyStatusClickHandler = useNavigateByRouterEventHandler(policyResponseRoutePath);
-
- const detailsResultsPolicy = useMemo(() => {
- return [
{
title: i18n.translate('xpack.securitySolution.endpoint.details.policy', {
- defaultMessage: 'Integration Policy',
+ defaultMessage: 'Policy',
}),
description: (
@@ -198,7 +160,7 @@ export const EndpointDetails = memo(
},
{
title: i18n.translate('xpack.securitySolution.endpoint.details.policyStatus', {
- defaultMessage: 'Policy Response',
+ defaultMessage: 'Policy Status',
}),
description: (
// https://github.com/elastic/eui/issues/4530
@@ -210,7 +172,7 @@ export const EndpointDetails = memo(
onClick={policyStatusClickHandler}
onClickAriaLabel={i18n.translate(
'xpack.securitySolution.endpoint.details.policyStatus',
- { defaultMessage: 'Policy Response' }
+ { defaultMessage: 'Policy Status' }
)}
>
@@ -223,10 +185,12 @@ export const EndpointDetails = memo(
),
},
- ];
- }, [details, policyResponseUri, policyStatus, policyStatusClickHandler, policyInfo]);
- const detailsResultsLower = useMemo(() => {
- return [
+ {
+ title: i18n.translate('xpack.securitySolution.endpoint.details.endpointVersion', {
+ defaultMessage: 'Endpoint Version',
+ }),
+ description: {details.agent.version} ,
+ },
{
title: i18n.translate('xpack.securitySolution.endpoint.details.ipAddress', {
defaultMessage: 'IP Address',
@@ -241,70 +205,23 @@ export const EndpointDetails = memo(
),
},
- {
- title: i18n.translate('xpack.securitySolution.endpoint.details.hostname', {
- defaultMessage: 'Hostname',
- }),
- description: {details.host.hostname} ,
- },
- {
- title: i18n.translate('xpack.securitySolution.endpoint.details.endpointVersion', {
- defaultMessage: 'Endpoint Version',
- }),
- description: {details.agent.version} ,
- },
];
- }, [details.agent.version, details.host.hostname, details.host.ip]);
+ }, [
+ details,
+ hostStatus,
+ policyResponseUri,
+ policyStatus,
+ policyStatusClickHandler,
+ policyInfo,
+ ]);
return (
<>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
>
);
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx
index c39a17e98c76a..7892c56fef806 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx
@@ -166,14 +166,14 @@ export const EndpointDetailsFlyout = memo(() => {
style={{ zIndex: 4001 }}
data-test-subj="endpointDetailsFlyout"
size="m"
- paddingSize="m"
+ paddingSize="l"
>
-
+
{hostDetailsLoading ? (
) : (
-
+
),
},
+ {
+ icon: 'gear',
+ key: 'agentPolicyReassignLink',
+ 'data-test-subj': 'agentPolicyReassignLink',
+ navigateAppId: 'fleet',
+ navigateOptions: {
+ path: `#${
+ pagePathGetters.fleet_agent_details({
+ agentId: fleetAgentId,
+ })[1]
+ }/activity?openReassignFlyout=true`,
+ },
+ href: `${getUrlForApp('fleet')}#${
+ pagePathGetters.fleet_agent_details({
+ agentId: fleetAgentId,
+ })[1]
+ }/activity?openReassignFlyout=true`,
+ children: (
+
+ ),
+ },
];
}
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
index aa1c47a3102d9..86f1e32e751ee 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
@@ -516,7 +516,6 @@ describe('when on the endpoint list page', () => {
describe('when there is a selected host in the url', () => {
let hostDetails: HostInfo;
- let elasticAgentId: string;
let renderAndWaitForData: () => Promise>;
const mockEndpointListApi = (mockedPolicyResponse?: HostPolicyResponse) => {
const {
@@ -546,8 +545,6 @@ describe('when on the endpoint list page', () => {
query_strategy_version,
};
- elasticAgentId = hostDetails.metadata.elastic.agent.id;
-
const policy = docGenerator.generatePolicyPackagePolicy();
policy.id = hostDetails.metadata.Endpoint.policy.applied.id;
@@ -738,37 +735,11 @@ describe('when on the endpoint list page', () => {
);
});
- it('should include the link to reassignment in Ingest', async () => {
- coreStart.application.getUrlForApp.mockReturnValue('/app/fleet');
- const renderResult = await renderAndWaitForData();
- const linkToReassign = await renderResult.findByTestId('endpointDetailsLinkToIngest');
- expect(linkToReassign).not.toBeNull();
- expect(linkToReassign.textContent).toEqual('Reassign Policy');
- expect(linkToReassign.getAttribute('href')).toEqual(
- `/app/fleet#/fleet/agents/${elasticAgentId}/activity?openReassignFlyout=true`
- );
- });
-
it('should show the Take Action button', async () => {
const renderResult = await renderAndWaitForData();
expect(renderResult.getByTestId('endpointDetailsActionsButton')).not.toBeNull();
});
- describe('when link to reassignment in Ingest is clicked', () => {
- beforeEach(async () => {
- coreStart.application.getUrlForApp.mockReturnValue('/app/fleet');
- const renderResult = await renderAndWaitForData();
- const linkToReassign = await renderResult.findByTestId('endpointDetailsLinkToIngest');
- reactTestingLibrary.act(() => {
- reactTestingLibrary.fireEvent.click(linkToReassign);
- });
- });
-
- it('should navigate to Ingest without full page refresh', () => {
- expect(coreStart.application.navigateToApp.mock.calls).toHaveLength(1);
- });
- });
-
describe('when showing host Policy Response panel', () => {
let renderResult: ReturnType;
beforeEach(async () => {
@@ -1139,5 +1110,12 @@ describe('when on the endpoint list page', () => {
const agentDetailsLink = await renderResult.findByTestId('agentDetailsLink');
expect(agentDetailsLink.getAttribute('href')).toEqual(`/app/fleet#/fleet/agents/${agentId}`);
});
+
+ it('navigates to the Ingest Agent Details page with policy reassign', async () => {
+ const agentPolicyReassignLink = await renderResult.findByTestId('agentPolicyReassignLink');
+ expect(agentPolicyReassignLink.getAttribute('href')).toEqual(
+ `/app/fleet#/fleet/agents/${agentId}/activity?openReassignFlyout=true`
+ );
+ });
});
});
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index b175b627ee1f0..25c6af15e08c3 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -20113,10 +20113,8 @@
"xpack.securitySolution.endpoint.details.endpointVersion": "エンドポイントバージョン",
"xpack.securitySolution.endpoint.details.errorBody": "フライアウトを終了して、利用可能なホストを選択してください。",
"xpack.securitySolution.endpoint.details.errorTitle": "ホストが見つかりませんでした",
- "xpack.securitySolution.endpoint.details.hostname": "ホスト名",
"xpack.securitySolution.endpoint.details.ipAddress": "IPアドレス",
"xpack.securitySolution.endpoint.details.lastSeen": "前回の認識",
- "xpack.securitySolution.endpoint.details.linkToIngestTitle": "ポリシーの再割り当て",
"xpack.securitySolution.endpoint.details.noPolicyResponse": "ポリシー応答がありません",
"xpack.securitySolution.endpoint.details.os": "OS",
"xpack.securitySolution.endpoint.details.policy": "統合ポリシー",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 438c70d839806..4a863160b61be 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -20411,10 +20411,8 @@
"xpack.securitySolution.endpoint.details.endpointVersion": "终端版本",
"xpack.securitySolution.endpoint.details.errorBody": "请退出浮出控件并选择可用主机。",
"xpack.securitySolution.endpoint.details.errorTitle": "找不到主机",
- "xpack.securitySolution.endpoint.details.hostname": "主机名",
"xpack.securitySolution.endpoint.details.ipAddress": "IP 地址",
"xpack.securitySolution.endpoint.details.lastSeen": "最后看到时间",
- "xpack.securitySolution.endpoint.details.linkToIngestTitle": "重新分配策略",
"xpack.securitySolution.endpoint.details.noPolicyResponse": "没有可用策略响应",
"xpack.securitySolution.endpoint.details.os": "OS",
"xpack.securitySolution.endpoint.details.policy": "集成策略",
diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts
index e55307ed5ef66..1a5158adbd695 100644
--- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts
+++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts
@@ -124,8 +124,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
describe('when the hostname is clicked on,', () => {
it('display the details flyout', async () => {
await (await testSubjects.find('hostnameCellLink')).click();
- await testSubjects.existOrFail('endpointDetailsUpperList');
- await testSubjects.existOrFail('endpointDetailsLowerList');
+ await testSubjects.existOrFail('endpointDetailsList');
});
it('updates the details flyout when a new hostname is selected from the list', async () => {
From 8ca7b55c150017216362b74d1c67f2860bf2dbf9 Mon Sep 17 00:00:00 2001
From: Byron Hulcher
Date: Tue, 15 Jun 2021 10:08:07 -0400
Subject: [PATCH 52/91] App Search: Domains Table for Crawler Overview
(#101515)
* Add delete route for crawler domains
* Added deleteDomain action to CrawlerOverviewLogic
* New DomainsTable component
* Replace EuiCode with DomainsTable in CrawlerOverview
* Fixing mock imports
* Fix spleling
* Fix i18n tag
* Clean up DomainsTable
* Clean up DomainsTable tests
* Clean up CrawlerOverview tests
* Clean up i18n tag in CrawlerOverviewLogic
* Utilize Kibana i18n and React for timestamp display
* Use elastic.co domain in tests
* Remove unneeded type casting
* Adjusting isDateToday check
* Apply suggestions from code review
* Update x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview_logic.ts
Co-authored-by: Constance
---
.../custom_formatted_timestamp.test.tsx | 36 ++++
.../components/custom_formatted_timestamp.tsx | 28 ++++
.../crawler/components/domains_table.test.tsx | 154 ++++++++++++++++++
.../crawler/components/domains_table.tsx | 130 +++++++++++++++
.../crawler/crawler_overview.test.tsx | 45 +++--
.../components/crawler/crawler_overview.tsx | 8 +-
.../crawler/crawler_overview_logic.test.ts | 100 ++++++++----
.../crawler/crawler_overview_logic.ts | 51 +++++-
.../public/applications/app_search/routes.ts | 2 +-
.../server/routes/app_search/crawler.test.ts | 56 +++++++
.../server/routes/app_search/crawler.ts | 18 ++
11 files changed, 553 insertions(+), 75 deletions(-)
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/custom_formatted_timestamp.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/custom_formatted_timestamp.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/domains_table.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/domains_table.tsx
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/custom_formatted_timestamp.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/custom_formatted_timestamp.test.tsx
new file mode 100644
index 0000000000000..8ad7b10685488
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/custom_formatted_timestamp.test.tsx
@@ -0,0 +1,36 @@
+/*
+ * 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 { shallow } from 'enzyme';
+
+import { FormattedRelative } from '@kbn/i18n/react';
+
+import { FormattedDateTime } from '../../../utils/formatted_date_time';
+
+import { CustomFormattedTimestamp } from './custom_formatted_timestamp';
+
+describe('CustomFormattedTimestamp', () => {
+ const mockToday = jest
+ .spyOn(global.Date, 'now')
+ .mockImplementation(() => new Date('1970-01-02').valueOf());
+
+ afterAll(() => mockToday.mockRestore());
+
+ it('uses a relative time format (x minutes ago) if the timestamp is from today', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(FormattedRelative).prop('value')).toEqual(new Date('1970-01-02'));
+ });
+
+ it('uses a date if the timestamp is before today', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(FormattedDateTime).prop('date')).toEqual(new Date('1970-01-01'));
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/custom_formatted_timestamp.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/custom_formatted_timestamp.tsx
new file mode 100644
index 0000000000000..da6d1ac9f2cb1
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/custom_formatted_timestamp.tsx
@@ -0,0 +1,28 @@
+/*
+ * 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 { FormattedRelative } from '@kbn/i18n/react';
+
+import { FormattedDateTime } from '../../../utils/formatted_date_time';
+
+interface CustomFormattedTimestampProps {
+ timestamp: string;
+}
+
+export const CustomFormattedTimestamp: React.FC = ({
+ timestamp,
+}) => {
+ const date = new Date(timestamp);
+ const isDateToday = date >= new Date(new Date(Date.now()).toDateString());
+ return isDateToday ? (
+
+ ) : (
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/domains_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/domains_table.test.tsx
new file mode 100644
index 0000000000000..aab2909d630ed
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/domains_table.test.tsx
@@ -0,0 +1,154 @@
+/*
+ * 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 { mockKibanaValues, setMockActions, setMockValues } from '../../../../__mocks__/kea_logic';
+import '../../../__mocks__/engine_logic.mock';
+
+import React from 'react';
+
+import { shallow, ShallowWrapper } from 'enzyme';
+
+import { EuiBasicTable, EuiButtonIcon, EuiInMemoryTable } from '@elastic/eui';
+
+import { mountWithIntl } from '../../../../test_helpers';
+
+import { CrawlerDomain } from '../types';
+
+import { DomainsTable } from './domains_table';
+
+const domains: CrawlerDomain[] = [
+ {
+ id: '1234',
+ documentCount: 9999,
+ url: 'elastic.co',
+ crawlRules: [],
+ entryPoints: [],
+ sitemaps: [],
+ lastCrawl: '2020-01-01T00:00:00-05:00',
+ createdOn: '2020-01-01T00:00:00-05:00',
+ },
+];
+
+const values = {
+ // EngineLogic
+ engineName: 'some-engine',
+ // CrawlerOverviewLogic
+ domains,
+ // AppLogic
+ myRole: { canManageEngineCrawler: false },
+};
+
+const actions = {
+ // CrawlerOverviewLogic
+ deleteDomain: jest.fn(),
+};
+
+describe('DomainsTable', () => {
+ let wrapper: ShallowWrapper;
+ let tableContent: string;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+ beforeAll(() => {
+ setMockValues(values);
+ setMockActions(actions);
+ wrapper = shallow( );
+ tableContent = mountWithIntl( )
+ .find(EuiInMemoryTable)
+ .text();
+ });
+
+ it('renders', () => {
+ expect(wrapper.find(EuiInMemoryTable)).toHaveLength(1);
+ });
+
+ describe('columns', () => {
+ const getTable = () => wrapper.find(EuiInMemoryTable).dive().find(EuiBasicTable).dive();
+
+ beforeEach(() => {
+ wrapper = shallow( );
+ tableContent = mountWithIntl( )
+ .find(EuiInMemoryTable)
+ .text();
+ });
+
+ it('renders a url column', () => {
+ expect(tableContent).toContain('elastic.co');
+ });
+
+ it('renders a last crawled column', () => {
+ expect(tableContent).toContain('Last activity');
+ expect(tableContent).toContain('Jan 1, 2020');
+ });
+
+ it('renders a document count column', () => {
+ expect(tableContent).toContain('Documents');
+ expect(tableContent).toContain('9,999');
+ });
+
+ describe('actions column', () => {
+ const getActions = () => getTable().find('ExpandedItemActions');
+ const getActionItems = () => getActions().dive().find('DefaultItemAction');
+
+ it('will hide the action buttons if the user cannot manage/delete engines', () => {
+ setMockValues({
+ ...values,
+ // AppLogic
+ myRole: { canManageEngineCrawler: false },
+ });
+ wrapper = shallow( );
+
+ expect(getActions()).toHaveLength(0);
+ });
+
+ describe('when the user can manage/delete engines', () => {
+ const getManageAction = () => getActionItems().at(0).dive().find(EuiButtonIcon);
+ const getDeleteAction = () => getActionItems().at(1).dive().find(EuiButtonIcon);
+
+ beforeEach(() => {
+ setMockValues({
+ ...values,
+ // AppLogic
+ myRole: { canManageEngineCrawler: true },
+ });
+ wrapper = shallow( );
+ });
+
+ describe('manage action', () => {
+ it('sends the user to the engine overview on click', () => {
+ const { navigateToUrl } = mockKibanaValues;
+
+ getManageAction().simulate('click');
+
+ expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/crawler/domains/1234');
+ });
+ });
+
+ describe('delete action', () => {
+ it('clicking the action and confirming deletes the domain', () => {
+ jest.spyOn(global, 'confirm').mockReturnValueOnce(true);
+
+ getDeleteAction().simulate('click');
+
+ expect(actions.deleteDomain).toHaveBeenCalledWith(
+ expect.objectContaining({ id: '1234' })
+ );
+ });
+
+ it('clicking the action and not confirming does not delete the engine', () => {
+ jest.spyOn(global, 'confirm').mockReturnValueOnce(false);
+
+ getDeleteAction().simulate('click');
+
+ expect(actions.deleteDomain).not.toHaveBeenCalled();
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/domains_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/domains_table.tsx
new file mode 100644
index 0000000000000..0da3b8462cfcf
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/domains_table.tsx
@@ -0,0 +1,130 @@
+/*
+ * 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 { useActions, useValues } from 'kea';
+
+import { EuiInMemoryTable, EuiBasicTableColumn } from '@elastic/eui';
+
+import { i18n } from '@kbn/i18n';
+
+import { FormattedNumber } from '@kbn/i18n/react';
+
+import { DELETE_BUTTON_LABEL, MANAGE_BUTTON_LABEL } from '../../../../shared/constants';
+import { KibanaLogic } from '../../../../shared/kibana';
+import { AppLogic } from '../../../app_logic';
+import { ENGINE_CRAWLER_DOMAIN_PATH } from '../../../routes';
+import { generateEnginePath } from '../../engine';
+import { CrawlerOverviewLogic } from '../crawler_overview_logic';
+import { CrawlerDomain } from '../types';
+
+import { CustomFormattedTimestamp } from './custom_formatted_timestamp';
+
+export const DomainsTable: React.FC = () => {
+ const { domains } = useValues(CrawlerOverviewLogic);
+
+ const { deleteDomain } = useActions(CrawlerOverviewLogic);
+
+ const {
+ myRole: { canManageEngineCrawler },
+ } = useValues(AppLogic);
+
+ const columns: Array> = [
+ {
+ field: 'url',
+ name: i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.domainURL',
+ {
+ defaultMessage: 'Domain URL',
+ }
+ ),
+ },
+ {
+ field: 'lastCrawl',
+ name: i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.lastActivity',
+ {
+ defaultMessage: 'Last activity',
+ }
+ ),
+ render: (lastCrawl: CrawlerDomain['lastCrawl']) =>
+ lastCrawl ? : '',
+ },
+ {
+ field: 'documentCount',
+ name: i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.documents',
+ {
+ defaultMessage: 'Documents',
+ }
+ ),
+ render: (documentCount: CrawlerDomain['documentCount']) => (
+
+ ),
+ },
+ ];
+
+ const actionsColumn: EuiBasicTableColumn = {
+ name: i18n.translate('xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.actions', {
+ defaultMessage: 'Actions',
+ }),
+ actions: [
+ {
+ name: MANAGE_BUTTON_LABEL,
+ description: i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.manage.buttonLabel',
+ {
+ defaultMessage: 'Manage this domain',
+ }
+ ),
+ type: 'icon',
+ icon: 'eye',
+ onClick: (domain) =>
+ KibanaLogic.values.navigateToUrl(
+ generateEnginePath(ENGINE_CRAWLER_DOMAIN_PATH, { domainId: domain.id })
+ ),
+ },
+ {
+ name: DELETE_BUTTON_LABEL,
+ description: i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.delete.buttonLabel',
+ {
+ defaultMessage: 'Delete this domain',
+ }
+ ),
+ type: 'icon',
+ icon: 'trash',
+ color: 'danger',
+ onClick: (domain) => {
+ if (
+ window.confirm(
+ i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.delete.confirmationPopupMessage',
+ {
+ defaultMessage:
+ 'Are you sure you want to remove the domain "{domainUrl}" and all of its settings?',
+ values: {
+ domainUrl: domain.url,
+ },
+ }
+ )
+ )
+ ) {
+ deleteDomain(domain);
+ }
+ },
+ },
+ ],
+ };
+
+ if (canManageEngineCrawler) {
+ columns.push(actionsColumn);
+ }
+
+ return ;
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.test.tsx
index cc3fa21f73309..affc2fd08e34c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.test.tsx
@@ -12,53 +12,46 @@ import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
-import { EuiCode } from '@elastic/eui';
-
import { Loading } from '../../../shared/loading';
import { rerender } from '../../../test_helpers';
+import { DomainsTable } from './components/domains_table';
import { CrawlerOverview } from './crawler_overview';
-const actions = {
- fetchCrawlerData: jest.fn(),
-};
+describe('CrawlerOverview', () => {
+ const mockActions = {
+ fetchCrawlerData: jest.fn(),
+ };
-const values = {
- dataLoading: false,
- domains: [],
-};
+ const mockValues = {
+ dataLoading: false,
+ domains: [],
+ };
-describe('CrawlerOverview', () => {
let wrapper: ShallowWrapper;
beforeEach(() => {
jest.clearAllMocks();
- setMockValues(values);
- setMockActions(actions);
+ setMockValues(mockValues);
+ setMockActions(mockActions);
wrapper = shallow( );
});
- it('renders', () => {
- expect(wrapper.find(EuiCode)).toHaveLength(1);
- });
-
it('calls fetchCrawlerData on page load', () => {
- expect(actions.fetchCrawlerData).toHaveBeenCalledTimes(1);
+ expect(mockActions.fetchCrawlerData).toHaveBeenCalledTimes(1);
});
- // TODO after DomainsTable is built in a future PR
- // it('contains a DomainsTable', () => {})
+ it('renders', () => {
+ expect(wrapper.find(DomainsTable)).toHaveLength(1);
- // TODO after CrawlRequestsTable is built in a future PR
- // it('containss a CrawlRequestsTable,() => {})
+ // TODO test for CrawlRequestsTable after it is built in a future PR
- // TODO after AddDomainForm is built in a future PR
- // it('contains an AddDomainForm' () => {})
+ // TODO test for AddDomainForm after it is built in a future PR
- // TODO after empty state is added in a future PR
- // it('has an empty state', () => {} )
+ // TODO test for empty state after it is built in a future PR
+ });
- it('shows an empty state when data is loading', () => {
+ it('shows a loading state when data is loading', () => {
setMockValues({ dataLoading: true });
rerender(wrapper);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx
index 5eeaaaef69605..14906378692ed 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx
@@ -9,17 +9,17 @@ import React, { useEffect } from 'react';
import { useActions, useValues } from 'kea';
-import { EuiCode, EuiPageHeader } from '@elastic/eui';
+import { EuiPageHeader } from '@elastic/eui';
import { FlashMessages } from '../../../shared/flash_messages';
-
import { Loading } from '../../../shared/loading';
+import { DomainsTable } from './components/domains_table';
import { CRAWLER_TITLE } from './constants';
import { CrawlerOverviewLogic } from './crawler_overview_logic';
export const CrawlerOverview: React.FC = () => {
- const { dataLoading, domains } = useValues(CrawlerOverviewLogic);
+ const { dataLoading } = useValues(CrawlerOverviewLogic);
const { fetchCrawlerData } = useActions(CrawlerOverviewLogic);
@@ -35,7 +35,7 @@ export const CrawlerOverview: React.FC = () => {
<>
- {JSON.stringify(domains, null, 2)}
+
>
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview_logic.test.ts
index f23322337766a..7ef5984960e26 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview_logic.test.ts
@@ -15,7 +15,14 @@ import '../../__mocks__/engine_logic.mock';
import { nextTick } from '@kbn/test/jest';
import { CrawlerOverviewLogic } from './crawler_overview_logic';
-import { CrawlerPolicies, CrawlerRules, CrawlRule } from './types';
+import {
+ CrawlerDataFromServer,
+ CrawlerDomain,
+ CrawlerPolicies,
+ CrawlerRules,
+ CrawlRule,
+} from './types';
+import { crawlerDataServerToClient } from './utils';
const DEFAULT_VALUES = {
dataLoading: true,
@@ -29,10 +36,26 @@ const DEFAULT_CRAWL_RULE: CrawlRule = {
pattern: '.*',
};
+const MOCK_SERVER_DATA: CrawlerDataFromServer = {
+ domains: [
+ {
+ id: '507f1f77bcf86cd799439011',
+ name: 'elastic.co',
+ created_on: 'Mon, 31 Aug 2020 17:00:00 +0000',
+ document_count: 13,
+ sitemaps: [],
+ entry_points: [],
+ crawl_rules: [],
+ },
+ ],
+};
+
+const MOCK_CLIENT_DATA = crawlerDataServerToClient(MOCK_SERVER_DATA);
+
describe('CrawlerOverviewLogic', () => {
const { mount } = new LogicMounter(CrawlerOverviewLogic);
const { http } = mockHttpValues;
- const { flashAPIErrors } = mockFlashMessageHelpers;
+ const { flashAPIErrors, setSuccessMessage } = mockFlashMessageHelpers;
beforeEach(() => {
jest.clearAllMocks();
@@ -44,13 +67,13 @@ describe('CrawlerOverviewLogic', () => {
});
describe('actions', () => {
- describe('onFetchCrawlerData', () => {
+ describe('onReceiveCrawlerData', () => {
const crawlerData = {
domains: [
{
id: '507f1f77bcf86cd799439011',
createdOn: 'Mon, 31 Aug 2020 17:00:00 +0000',
- url: 'moviedatabase.com',
+ url: 'elastic.co',
documentCount: 13,
sitemaps: [],
entryPoints: [],
@@ -61,7 +84,7 @@ describe('CrawlerOverviewLogic', () => {
};
beforeEach(() => {
- CrawlerOverviewLogic.actions.onFetchCrawlerData(crawlerData);
+ CrawlerOverviewLogic.actions.onReceiveCrawlerData(crawlerData);
});
it('should set all received data as top-level values', () => {
@@ -76,41 +99,17 @@ describe('CrawlerOverviewLogic', () => {
describe('listeners', () => {
describe('fetchCrawlerData', () => {
- it('calls onFetchCrawlerData with retrieved data that has been converted from server to client', async () => {
- jest.spyOn(CrawlerOverviewLogic.actions, 'onFetchCrawlerData');
-
- http.get.mockReturnValue(
- Promise.resolve({
- domains: [
- {
- id: '507f1f77bcf86cd799439011',
- name: 'moviedatabase.com',
- created_on: 'Mon, 31 Aug 2020 17:00:00 +0000',
- document_count: 13,
- sitemaps: [],
- entry_points: [],
- crawl_rules: [],
- },
- ],
- })
- );
+ it('calls onReceiveCrawlerData with retrieved data that has been converted from server to client', async () => {
+ jest.spyOn(CrawlerOverviewLogic.actions, 'onReceiveCrawlerData');
+
+ http.get.mockReturnValue(Promise.resolve(MOCK_SERVER_DATA));
CrawlerOverviewLogic.actions.fetchCrawlerData();
await nextTick();
expect(http.get).toHaveBeenCalledWith('/api/app_search/engines/some-engine/crawler');
- expect(CrawlerOverviewLogic.actions.onFetchCrawlerData).toHaveBeenCalledWith({
- domains: [
- {
- id: '507f1f77bcf86cd799439011',
- createdOn: 'Mon, 31 Aug 2020 17:00:00 +0000',
- url: 'moviedatabase.com',
- documentCount: 13,
- sitemaps: [],
- entryPoints: [],
- crawlRules: [],
- },
- ],
- });
+ expect(CrawlerOverviewLogic.actions.onReceiveCrawlerData).toHaveBeenCalledWith(
+ MOCK_CLIENT_DATA
+ );
});
it('calls flashApiErrors when there is an error', async () => {
@@ -121,5 +120,34 @@ describe('CrawlerOverviewLogic', () => {
expect(flashAPIErrors).toHaveBeenCalledWith('error');
});
});
+
+ describe('deleteDomain', () => {
+ it('calls onReceiveCrawlerData with retrieved data that has been converted from server to client', async () => {
+ jest.spyOn(CrawlerOverviewLogic.actions, 'onReceiveCrawlerData');
+
+ http.delete.mockReturnValue(Promise.resolve(MOCK_SERVER_DATA));
+ CrawlerOverviewLogic.actions.deleteDomain({ id: '1234' } as CrawlerDomain);
+ await nextTick();
+
+ expect(http.delete).toHaveBeenCalledWith(
+ '/api/app_search/engines/some-engine/crawler/domains/1234',
+ {
+ query: { respond_with: 'crawler_details' },
+ }
+ );
+ expect(CrawlerOverviewLogic.actions.onReceiveCrawlerData).toHaveBeenCalledWith(
+ MOCK_CLIENT_DATA
+ );
+ expect(setSuccessMessage).toHaveBeenCalled();
+ });
+
+ it('calls flashApiErrors when there is an error', async () => {
+ http.delete.mockReturnValue(Promise.reject('error'));
+ CrawlerOverviewLogic.actions.deleteDomain({ id: '1234' } as CrawlerDomain);
+ await nextTick();
+
+ expect(flashAPIErrors).toHaveBeenCalledWith('error');
+ });
+ });
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview_logic.ts
index 6f04ade5962eb..dceb4e205487d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview_logic.ts
@@ -7,22 +7,36 @@
import { kea, MakeLogicType } from 'kea';
-import { flashAPIErrors } from '../../../shared/flash_messages';
+import { i18n } from '@kbn/i18n';
+
+import { flashAPIErrors, setSuccessMessage } from '../../../shared/flash_messages';
import { HttpLogic } from '../../../shared/http';
import { EngineLogic } from '../engine';
-import { CrawlerData, CrawlerDataFromServer, CrawlerDomain } from './types';
+import { CrawlerData, CrawlerDomain } from './types';
import { crawlerDataServerToClient } from './utils';
+export const DELETE_DOMAIN_MESSAGE = (domainUrl: string) =>
+ i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.delete.successMessage',
+ {
+ defaultMessage: 'Successfully deleted "{domainUrl}"',
+ values: {
+ domainUrl,
+ },
+ }
+ );
+
interface CrawlerOverviewValues {
dataLoading: boolean;
domains: CrawlerDomain[];
}
interface CrawlerOverviewActions {
+ deleteDomain(domain: CrawlerDomain): { domain: CrawlerDomain };
fetchCrawlerData(): void;
- onFetchCrawlerData(data: CrawlerData): { data: CrawlerData };
+ onReceiveCrawlerData(data: CrawlerData): { data: CrawlerData };
}
export const CrawlerOverviewLogic = kea<
@@ -30,20 +44,21 @@ export const CrawlerOverviewLogic = kea<
>({
path: ['enterprise_search', 'app_search', 'crawler', 'crawler_overview'],
actions: {
+ deleteDomain: (domain) => ({ domain }),
fetchCrawlerData: true,
- onFetchCrawlerData: (data) => ({ data }),
+ onReceiveCrawlerData: (data) => ({ data }),
},
reducers: {
dataLoading: [
true,
{
- onFetchCrawlerData: () => false,
+ onReceiveCrawlerData: () => false,
},
],
domains: [
[],
{
- onFetchCrawlerData: (_, { data: { domains } }) => domains,
+ onReceiveCrawlerData: (_, { data: { domains } }) => domains,
},
],
},
@@ -54,8 +69,28 @@ export const CrawlerOverviewLogic = kea<
try {
const response = await http.get(`/api/app_search/engines/${engineName}/crawler`);
- const crawlerData = crawlerDataServerToClient(response as CrawlerDataFromServer);
- actions.onFetchCrawlerData(crawlerData);
+ const crawlerData = crawlerDataServerToClient(response);
+ actions.onReceiveCrawlerData(crawlerData);
+ } catch (e) {
+ flashAPIErrors(e);
+ }
+ },
+ deleteDomain: async ({ domain }) => {
+ const { http } = HttpLogic.values;
+ const { engineName } = EngineLogic.values;
+
+ try {
+ const response = await http.delete(
+ `/api/app_search/engines/${engineName}/crawler/domains/${domain.id}`,
+ {
+ query: {
+ respond_with: 'crawler_details',
+ },
+ }
+ );
+ const crawlerData = crawlerDataServerToClient(response);
+ actions.onReceiveCrawlerData(crawlerData);
+ setSuccessMessage(DELETE_DOMAIN_MESSAGE(domain.url));
} catch (e) {
flashAPIErrors(e);
}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts
index c8fb009fb31da..bd5bdb7b2f665 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts
@@ -37,7 +37,7 @@ export const ENGINE_SCHEMA_PATH = `${ENGINE_PATH}/schema`;
export const ENGINE_REINDEX_JOB_PATH = `${ENGINE_SCHEMA_PATH}/reindex_job/:reindexJobId`;
export const ENGINE_CRAWLER_PATH = `${ENGINE_PATH}/crawler`;
-// TODO: Crawler sub-pages
+export const ENGINE_CRAWLER_DOMAIN_PATH = `${ENGINE_CRAWLER_PATH}/domains/:domainId`;
export const META_ENGINE_CREATION_PATH = '/meta_engine_creation';
export const META_ENGINE_SOURCE_ENGINES_PATH = `${ENGINE_PATH}/engines`;
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.test.ts
index 626a107b6942b..06a206017fbd1 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.test.ts
@@ -31,5 +31,61 @@ describe('crawler routes', () => {
path: '/api/as/v0/engines/:name/crawler',
});
});
+
+ it('validates correctly with name', () => {
+ const request = { params: { name: 'some-engine' } };
+ mockRouter.shouldValidate(request);
+ });
+
+ it('fails validation without name', () => {
+ const request = { params: {} };
+ mockRouter.shouldThrow(request);
+ });
+ });
+
+ describe('DELETE /api/app_search/engines/{name}/crawler/domains/{id}', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'delete',
+ path: '/api/app_search/engines/{name}/crawler/domains/{id}',
+ });
+
+ registerCrawlerRoutes({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ it('creates a request to enterprise search', () => {
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/api/as/v0/engines/:name/crawler/domains/:id',
+ });
+ });
+
+ it('validates correctly with name and id', () => {
+ const request = { params: { name: 'some-engine', id: '1234' } };
+ mockRouter.shouldValidate(request);
+ });
+
+ it('fails validation without name', () => {
+ const request = { params: { id: '1234' } };
+ mockRouter.shouldThrow(request);
+ });
+
+ it('fails validation without id', () => {
+ const request = { params: { name: 'test-engine' } };
+ mockRouter.shouldThrow(request);
+ });
+
+ it('accepts a query param', () => {
+ const request = {
+ params: { name: 'test-engine', id: '1234' },
+ query: { respond_with: 'crawler_details' },
+ };
+ mockRouter.shouldValidate(request);
+ });
});
});
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.ts
index 15b8340b07d4e..6c8ed7a49c64a 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.ts
@@ -26,4 +26,22 @@ export function registerCrawlerRoutes({
path: '/api/as/v0/engines/:name/crawler',
})
);
+
+ router.delete(
+ {
+ path: '/api/app_search/engines/{name}/crawler/domains/{id}',
+ validate: {
+ params: schema.object({
+ name: schema.string(),
+ id: schema.string(),
+ }),
+ query: schema.object({
+ respond_with: schema.maybe(schema.string()),
+ }),
+ },
+ },
+ enterpriseSearchRequestHandler.createRequest({
+ path: '/api/as/v0/engines/:name/crawler/domains/:id',
+ })
+ );
}
From c4099850908b4bcf2d56ea7fb8641f421199161c Mon Sep 17 00:00:00 2001
From: Anton Dosov
Date: Tue, 15 Jun 2021 16:33:41 +0200
Subject: [PATCH 53/91] Convert AppServices management pages to
KibanaPageTemplate (#101779)
---
.../create_index_pattern_wizard.test.tsx.snap | 344 ++++-----
.../header/__snapshots__/header.test.tsx.snap | 711 +++++++++++-------
.../components/header/header.tsx | 17 +-
.../create_index_pattern_wizard.tsx | 25 +-
.../create_edit_field/create_edit_field.tsx | 38 +-
.../edit_index_pattern/edit_index_pattern.tsx | 43 +-
.../index_header/index_header.tsx | 76 +-
.../empty_index_pattern_prompt.test.tsx.snap | 2 +
.../empty_index_pattern_prompt.tsx | 2 +
.../__snapshots__/empty_state.test.tsx.snap | 2 +
.../empty_state/empty_state.tsx | 4 +-
.../index_pattern_table.tsx | 54 +-
.../search/sessions_mgmt/components/main.tsx | 81 +-
.../public/components/report_listing.tsx | 46 +-
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
16 files changed, 768 insertions(+), 679 deletions(-)
diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap
index 21248ac9d1dc0..38a9e47014416 100644
--- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap
+++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap
@@ -14,46 +14,46 @@ exports[`CreateIndexPatternWizard defaults to the loading state 1`] = `
exports[`CreateIndexPatternWizard renders index pattern step when there are indices 1`] = `
-
-
-
-
+
+
-
+ }
+ showSystemIndices={true}
+ />
-
-
-
-
+
+
-
+ }
+ showSystemIndices={true}
+ />
-
-
-
-
+
+
-
+ }
+ />
-
-
-
-
+
+
-
+ }
+ showSystemIndices={true}
+ />
-
-
-
-
+
+
-
+ }
+ showSystemIndices={true}
+ />
}
>
-
-
-
+
Create test index pattern
-
-
-
- Beta
-
-
-
-
-
+
+
+
+
+ }
+ >
+
-
-
+ Create test index pattern
+
+
+
+
+
+ }
+ responsive={true}
>
-
-
- multiple
- ,
- "single":
- filebeat-4-3-22
- ,
- "star":
- filebeat-*
- ,
- }
- }
+
+
-
- An index pattern can match a single source, for example,
-
-
-
-
- filebeat-4-3-22
-
-
-
-
- , or
-
- multiple
-
- data sources,
-
-
+
+
-
-
- filebeat-*
-
-
-
-
- .
-
-
-
-
+
+ Create test index pattern
+
+
+
+ Beta
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
-
- Read documentation
-
-
-
-
-
-
-
-
-
-
-
- Test prompt
-
-
+
+
+ multiple
+ ,
+ "single":
+ filebeat-4-3-22
+ ,
+ "star":
+ filebeat-*
+ ,
+ }
+ }
+ >
+
+ An index pattern can match a single source, for example,
+
+
+
+
+ filebeat-4-3-22
+
+
+
+
+ , or
+
+ multiple
+
+ data sources,
+
+
+
+
+ filebeat-*
+
+
+
+
+ .
+
+
+
+
+
+
+
+ Read documentation
+
+
+
+
+
+
+
+
+
+
+
+ Test prompt
+
+
+
+
+
+
`;
@@ -146,100 +203,145 @@ exports[`Header should render normally 1`] = `
}
indexPatternName="test index pattern"
>
-
-
-
+
Create test index pattern
-
-
-
+ }
+ >
+
-
-
+ Create test index pattern
+
+ }
+ responsive={true}
>
-
-
- multiple
- ,
- "single":
- filebeat-4-3-22
- ,
- "star":
- filebeat-*
- ,
- }
- }
+
+
-
- An index pattern can match a single source, for example,
-
-
-
-
- filebeat-4-3-22
-
-
-
-
- , or
-
- multiple
-
- data sources,
-
-
+
+
-
-
- filebeat-*
-
-
-
-
- .
-
-
-
-
+
+ Create test index pattern
+
+
+
+
+
+
+
-
-
+
+
+
+
-
- Read documentation
-
-
-
-
-
-
-
-
+
+
+ multiple
+ ,
+ "single":
+ filebeat-4-3-22
+ ,
+ "star":
+ filebeat-*
+ ,
+ }
+ }
+ >
+
+ An index pattern can match a single source, for example,
+
+
+
+
+ filebeat-4-3-22
+
+
+
+
+ , or
+
+ multiple
+
+ data sources,
+
+
+
+
+ filebeat-*
+
+
+
+
+ .
+
+
+
+
+
+
+
+ Read documentation
+
+
+
+
+
+
+
+
+
+
+
+
`;
@@ -254,99 +356,144 @@ exports[`Header should render without including system indices 1`] = `
}
indexPatternName="test index pattern"
>
-
-
-
+
Create test index pattern
-
-
-
+ }
+ >
+
-
-
+ Create test index pattern
+
+ }
+ responsive={true}
>
-
-
- multiple
- ,
- "single":
- filebeat-4-3-22
- ,
- "star":
- filebeat-*
- ,
- }
- }
+
+
-
- An index pattern can match a single source, for example,
-
-
-
-
- filebeat-4-3-22
-
-
-
-
- , or
-
- multiple
-
- data sources,
-
-
+
+
-
-
- filebeat-*
-
-
-
-
- .
-
-
-
-
+
+ Create test index pattern
+
+
+
+
+
+
+
-
-
+
+
+
+
-
- Read documentation
-
-
-
-
-
-
-
-
+
+
+ multiple
+ ,
+ "single":
+ filebeat-4-3-22
+ ,
+ "star":
+ filebeat-*
+ ,
+ }
+ }
+ >
+
+ An index pattern can match a single source, for example,
+
+
+
+
+ filebeat-4-3-22
+
+
+
+
+ , or
+
+ multiple
+
+ data sources,
+
+
+
+
+ filebeat-*
+
+
+
+
+ .
+
+
+
+
+
+
+
+ Read documentation
+
+
+
+
+
+
+
+
+
+
+
+
`;
diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx
index a7e3b2ded75dc..c708bd3cac33e 100644
--- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx
+++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx
@@ -8,7 +8,7 @@
import React from 'react';
-import { EuiBetaBadge, EuiSpacer, EuiTitle, EuiText, EuiCode, EuiLink } from '@elastic/eui';
+import { EuiBetaBadge, EuiCode, EuiLink, EuiPageHeader, EuiSpacer, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -39,9 +39,9 @@ export const Header = ({
changeTitle(createIndexPatternHeader);
return (
-
-
-
+
{createIndexPatternHeader}
{isBeta ? (
<>
@@ -53,9 +53,10 @@ export const Header = ({
/>
>
) : null}
-
-
-
+ >
+ }
+ bottomBorder
+ >
) : null}
-
+
);
};
diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx
index 633906feb785b..5bc53105dbcf8 100644
--- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx
+++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx
@@ -6,17 +6,12 @@
* Side Public License, v 1.
*/
-import React, { ReactElement, Component } from 'react';
-
-import {
- EuiGlobalToastList,
- EuiGlobalToastListToast,
- EuiPageContent,
- EuiHorizontalRule,
-} from '@elastic/eui';
+import React, { Component, ReactElement } from 'react';
+
+import { EuiGlobalToastList, EuiGlobalToastListToast, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
-import { withRouter, RouteComponentProps } from 'react-router-dom';
+import { RouteComponentProps, withRouter } from 'react-router-dom';
import { DocLinksStart } from 'src/core/public';
import { StepIndexPattern } from './components/step_index_pattern';
import { StepTimeField } from './components/step_time_field';
@@ -227,9 +222,9 @@ export class CreateIndexPatternWizard extends Component<
const initialQuery = new URLSearchParams(location.search).get('id') || undefined;
return (
-
+ <>
{header}
-
+
-
+ >
);
}
if (step === 2) {
return (
-
+ <>
{header}
-
+
-
+ >
);
}
diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx
index 5aa9853c5e766..0c0adc6dd5029 100644
--- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx
+++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx
@@ -7,15 +7,15 @@
*/
import React from 'react';
-import { withRouter, RouteComponentProps } from 'react-router-dom';
+import { RouteComponentProps, withRouter } from 'react-router-dom';
-import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
+import { EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { IndexPattern, IndexPatternField } from '../../../../../../plugins/data/public';
import { useKibana } from '../../../../../../plugins/kibana_react/public';
import { IndexPatternManagmentContext } from '../../../types';
import { IndexHeader } from '../index_header';
-import { TAB_SCRIPTED_FIELDS, TAB_INDEXED_FIELDS } from '../constants';
+import { TAB_INDEXED_FIELDS, TAB_SCRIPTED_FIELDS } from '../constants';
import { FieldEditor } from '../../field_editor';
@@ -76,26 +76,18 @@ export const CreateEditField = withRouter(
if (spec) {
return (
-
-
-
-
-
-
-
-
-
-
+ <>
+
+
+
+ >
);
} else {
return <>>;
diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx
index e314c00bc8176..6609605da87d1 100644
--- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx
+++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx
@@ -17,7 +17,6 @@ import {
EuiText,
EuiLink,
EuiCallOut,
- EuiPanel,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -145,15 +144,13 @@ export const EditIndexPattern = withRouter(
const kibana = useKibana();
const docsUrl = kibana.services.docLinks!.links.elasticsearch.mapping;
return (
-
-
-
-
+
+
{showTagsSection && (
{Boolean(indexPattern.timeFieldName) && (
@@ -193,19 +190,19 @@ export const EditIndexPattern = withRouter(
>
)}
-
- {
- setFields(indexPattern.getNonScriptedFields());
- }}
- />
-
-
+
+
+
{
+ setFields(indexPattern.getNonScriptedFields());
+ }}
+ />
+
);
}
);
diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/index_header/index_header.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/index_header/index_header.tsx
index 482cd574c8f1d..c141c228a68f2 100644
--- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/index_header/index_header.tsx
+++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/index_header/index_header.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiFlexGroup, EuiToolTip, EuiFlexItem, EuiTitle, EuiButtonIcon } from '@elastic/eui';
+import { EuiButtonIcon, EuiPageHeader, EuiToolTip } from '@elastic/eui';
import { IIndexPattern } from 'src/plugins/data/public';
interface IndexHeaderProps {
@@ -40,50 +40,42 @@ const removeTooltip = i18n.translate('indexPatternManagement.editIndexPattern.re
defaultMessage: 'Remove index pattern.',
});
-export function IndexHeader({
+export const IndexHeader: React.FC = ({
defaultIndex,
indexPattern,
setDefault,
deleteIndexPatternClick,
-}: IndexHeaderProps) {
+ children,
+}) => {
return (
-
-
-
- {indexPattern.title}
-
-
-
-
- {defaultIndex !== indexPattern.id && setDefault && (
-
-
-
-
-
- )}
-
- {deleteIndexPatternClick && (
-
-
-
-
-
- )}
-
-
-
+ {indexPattern.title}}
+ rightSideItems={[
+ defaultIndex !== indexPattern.id && setDefault && (
+
+
+
+ ),
+ deleteIndexPatternClick && (
+
+
+
+ ),
+ ].filter(Boolean)}
+ >
+ {children}
+
);
-}
+};
diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/__snapshots__/empty_index_pattern_prompt.test.tsx.snap b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/__snapshots__/empty_index_pattern_prompt.test.tsx.snap
index c5e6d1220d8bf..bc69fa29e6904 100644
--- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/__snapshots__/empty_index_pattern_prompt.test.tsx.snap
+++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_index_pattern_prompt/__snapshots__/empty_index_pattern_prompt.test.tsx.snap
@@ -3,9 +3,11 @@
exports[`EmptyIndexPatternPrompt should render normally 1`] = `
diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap
index 1310488c65fab..957c94c80680d 100644
--- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap
+++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap
@@ -4,9 +4,11 @@ exports[`EmptyState should render normally 1`] = `
diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx
index 240e732752916..c05f6a1f193b7 100644
--- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx
+++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx
@@ -63,8 +63,10 @@ export const EmptyState = ({
diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx b/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx
index f018294f27c84..6bd06528084ce 100644
--- a/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx
+++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx
@@ -8,24 +8,20 @@
import {
EuiBadge,
+ EuiBadgeGroup,
EuiButtonEmpty,
- EuiFlexGroup,
- EuiFlexItem,
EuiInMemoryTable,
+ EuiPageHeader,
EuiSpacer,
- EuiText,
- EuiBadgeGroup,
- EuiPageContent,
- EuiTitle,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-import { withRouter, RouteComponentProps } from 'react-router-dom';
-import React, { useState, useEffect } from 'react';
+import { RouteComponentProps, withRouter } from 'react-router-dom';
+import React, { useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { reactRouterNavigate, useKibana } from '../../../../../plugins/kibana_react/public';
import { IndexPatternManagmentContext } from '../../types';
import { CreateButton } from '../create_button';
-import { IndexPatternTableItem, IndexPatternCreationOption } from '../types';
+import { IndexPatternCreationOption, IndexPatternTableItem } from '../types';
import { getIndexPatterns } from '../utils';
import { getListBreadcrumbs } from '../breadcrumbs';
import { EmptyState } from './empty_state';
@@ -54,10 +50,6 @@ const search = {
},
};
-const ariaRegion = i18n.translate('indexPatternManagement.editIndexPatternLiveRegionAriaLabel', {
- defaultMessage: 'Index patterns',
-});
-
const title = i18n.translate('indexPatternManagement.indexPatternTable.title', {
defaultMessage: 'Index patterns',
});
@@ -197,25 +189,21 @@ export const IndexPatternTable = ({ canSave, history }: Props) => {
}
return (
-
-
-
-
- {title}
-
-
-
-
-
-
-
-
- {createButton}
-
-
+
+
+ }
+ bottomBorder
+ rightSideItems={[createButton]}
+ />
+
+
+
{
sorting={sorting}
search={search}
/>
-
+
);
};
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx
index 0e5be4b5a51e9..c398c92abf3a9 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx
@@ -5,25 +5,15 @@
* 2.0.
*/
-import {
- EuiButtonEmpty,
- EuiFlexGroup,
- EuiFlexItem,
- EuiHorizontalRule,
- EuiPageBody,
- EuiPageContent,
- EuiSpacer,
- EuiTitle,
-} from '@elastic/eui';
+import { EuiButtonEmpty, EuiPageHeader, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import type { CoreStart, HttpStart } from 'kibana/public';
import React from 'react';
import type { SessionsConfigSchema } from '../';
+import { IManagementSectionsPluginsSetup } from '../';
import type { SearchSessionsMgmtAPI } from '../lib/api';
import type { AsyncSearchIntroDocumentation } from '../lib/documentation';
-import { TableText } from './';
import { SearchSessionsMgmtTable } from './table';
-import { IManagementSectionsPluginsSetup } from '../';
interface Props {
documentation: AsyncSearchIntroDocumentation;
@@ -37,46 +27,37 @@ interface Props {
export function SearchSessionsMgmtMain({ documentation, ...tableProps }: Props) {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ <>
+
+ }
+ description={
+
+ }
+ bottomBorder
+ rightSideItems={[
+
-
-
-
-
+ ,
+ ]}
+ />
-
-
-
+
+
+ >
);
}
diff --git a/x-pack/plugins/reporting/public/components/report_listing.tsx b/x-pack/plugins/reporting/public/components/report_listing.tsx
index 6442c81a47184..618c91fba0715 100644
--- a/x-pack/plugins/reporting/public/components/report_listing.tsx
+++ b/x-pack/plugins/reporting/public/components/report_listing.tsx
@@ -9,11 +9,10 @@ import {
EuiBasicTable,
EuiFlexGroup,
EuiFlexItem,
- EuiPageContent,
+ EuiPageHeader,
EuiSpacer,
EuiText,
EuiTextColor,
- EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
@@ -139,38 +138,29 @@ class ReportListingUi extends Component {
public render() {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {this.renderTable()}
-
+ <>
+
+ }
+ description={
+
+ }
+ />
+
+
+ {this.renderTable()}
+
-
+ >
);
}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 25c6af15e08c3..676d6d6b7e351 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -3028,7 +3028,6 @@
"indexPatternManagement.editIndexPattern.timeFilterHeader": "時刻フィールド:「{timeFieldName}」",
"indexPatternManagement.editIndexPattern.timeFilterLabel.mappingAPILink": "フィールドマッピング",
"indexPatternManagement.editIndexPattern.timeFilterLabel.timeFilterDetail": "{indexPatternTitle}でフィールドを表示して編集します。型や検索可否などのフィールド属性はElasticsearchで{mappingAPILink}に基づきます。",
- "indexPatternManagement.editIndexPatternLiveRegionAriaLabel": "インデックスパターン",
"indexPatternManagement.emptyIndexPatternPrompt.documentation": "ドキュメンテーションを表示",
"indexPatternManagement.emptyIndexPatternPrompt.indexPatternExplanation": "Kibanaでは、検索するインデックスを特定するためにインデックスパターンが必要です。インデックスパターンは、昨日のログデータなど特定のインデックス、またはログデータを含むすべてのインデックスを参照できます。",
"indexPatternManagement.emptyIndexPatternPrompt.learnMore": "詳細について",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 4a863160b61be..8b558620aa897 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -3049,7 +3049,6 @@
"indexPatternManagement.editIndexPattern.timeFilterHeader": "时间字段:“{timeFieldName}”",
"indexPatternManagement.editIndexPattern.timeFilterLabel.mappingAPILink": "字段映射",
"indexPatternManagement.editIndexPattern.timeFilterLabel.timeFilterDetail": "查看和编辑 {indexPatternTitle} 中的字段。字段属性,如类型和可搜索性,基于 Elasticsearch 中的 {mappingAPILink}。",
- "indexPatternManagement.editIndexPatternLiveRegionAriaLabel": "索引模式",
"indexPatternManagement.emptyIndexPatternPrompt.documentation": "阅读文档",
"indexPatternManagement.emptyIndexPatternPrompt.indexPatternExplanation": "Kibana 需要索引模式,以识别您要浏览的索引。索引模式可以指向特定索引 (例如昨天的日志数据) ,或包含日志数据的所有索引。",
"indexPatternManagement.emptyIndexPatternPrompt.learnMore": "希望了解详情?",
From 7dfe2030ded920c5afece8f9db12d5d519ea0ee2 Mon Sep 17 00:00:00 2001
From: Pablo Machado
Date: Tue, 15 Jun 2021 16:55:41 +0200
Subject: [PATCH 54/91] [Core] Update getUrlForApp to support deepLinks
(#102172)
* [Core] Update getUrlForApp to support deepLinks
* Add unit tests to getUrlForApp with deepLinkId parameter
---
...re-public.applicationstart.geturlforapp.md | 3 +-
.../application/application_service.test.ts | 50 +++++++++++++++++++
.../application/application_service.tsx | 13 ++++-
src/core/public/application/types.ts | 5 +-
src/core/public/public.api.md | 1 +
5 files changed, 69 insertions(+), 3 deletions(-)
diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.geturlforapp.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.geturlforapp.md
index 1eaf00c7a678d..6229aeb9238e8 100644
--- a/docs/development/core/public/kibana-plugin-core-public.applicationstart.geturlforapp.md
+++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.geturlforapp.md
@@ -16,6 +16,7 @@ Note that when generating absolute urls, the origin (protocol, host and port) ar
getUrlForApp(appId: string, options?: {
path?: string;
absolute?: boolean;
+ deepLinkId?: string;
}): string;
```
@@ -24,7 +25,7 @@ getUrlForApp(appId: string, options?: {
| Parameter | Type | Description |
| --- | --- | --- |
| appId | string
| |
-| options | {
path?: string;
absolute?: boolean;
}
| |
+| options | {
path?: string;
absolute?: boolean;
deepLinkId?: string;
}
| |
Returns:
diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts
index 5658d3f626077..3ed164088bf5c 100644
--- a/src/core/public/application/application_service.test.ts
+++ b/src/core/public/application/application_service.test.ts
@@ -497,6 +497,56 @@ describe('#start()', () => {
expect(getUrlForApp('app1', { path: 'deep/link///' })).toBe('/base-path/app/app1/deep/link');
});
+ describe('deepLinkId option', () => {
+ it('ignores the deepLinkId parameter if it is unknown', async () => {
+ service.setup(setupDeps);
+
+ service.setup(setupDeps);
+ const { getUrlForApp } = await service.start(startDeps);
+
+ expect(getUrlForApp('app1', { deepLinkId: 'unkown-deep-link' })).toBe(
+ '/base-path/app/app1'
+ );
+ });
+
+ it('creates URLs with deepLinkId parameter', async () => {
+ const { register } = service.setup(setupDeps);
+
+ register(
+ Symbol(),
+ createApp({
+ id: 'app1',
+ appRoute: '/custom/app-path',
+ deepLinks: [{ id: 'dl1', title: 'deep link 1', path: '/deep-link' }],
+ })
+ );
+
+ const { getUrlForApp } = await service.start(startDeps);
+
+ expect(getUrlForApp('app1', { deepLinkId: 'dl1' })).toBe(
+ '/base-path/custom/app-path/deep-link'
+ );
+ });
+
+ it('creates URLs with deepLinkId and path parameters', async () => {
+ const { register } = service.setup(setupDeps);
+
+ register(
+ Symbol(),
+ createApp({
+ id: 'app1',
+ appRoute: '/custom/app-path',
+ deepLinks: [{ id: 'dl1', title: 'deep link 1', path: '/deep-link' }],
+ })
+ );
+
+ const { getUrlForApp } = await service.start(startDeps);
+ expect(getUrlForApp('app1', { deepLinkId: 'dl1', path: 'foo/bar' })).toBe(
+ '/base-path/custom/app-path/deep-link/foo/bar'
+ );
+ });
+ });
+
it('does not append trailing slash if hash is provided in path parameter', async () => {
service.setup(setupDeps);
const { getUrlForApp } = await service.start(startDeps);
diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx
index 32d45b32c32ff..8c6090caabce1 100644
--- a/src/core/public/application/application_service.tsx
+++ b/src/core/public/application/application_service.tsx
@@ -282,8 +282,19 @@ export class ApplicationService {
history: this.history!,
getUrlForApp: (
appId,
- { path, absolute = false }: { path?: string; absolute?: boolean } = {}
+ {
+ path,
+ absolute = false,
+ deepLinkId,
+ }: { path?: string; absolute?: boolean; deepLinkId?: string } = {}
) => {
+ if (deepLinkId) {
+ const deepLinkPath = getAppDeepLinkPath(availableMounters, appId, deepLinkId);
+ if (deepLinkPath) {
+ path = appendAppPath(deepLinkPath, path);
+ }
+ }
+
const relUrl = http.basePath.prepend(getAppUrl(availableMounters, appId, path));
return absolute ? relativeToAbsolute(relUrl) : relUrl;
},
diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts
index 60b0dbf158dd9..5803f2e3779ab 100644
--- a/src/core/public/application/types.ts
+++ b/src/core/public/application/types.ts
@@ -780,7 +780,10 @@ export interface ApplicationStart {
* @param options.path - optional path inside application to deep link to
* @param options.absolute - if true, will returns an absolute url instead of a relative one
*/
- getUrlForApp(appId: string, options?: { path?: string; absolute?: boolean }): string;
+ getUrlForApp(
+ appId: string,
+ options?: { path?: string; absolute?: boolean; deepLinkId?: string }
+ ): string;
/**
* An observable that emits the current application id and each subsequent id update.
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 235110aeb4633..d3426b50f7614 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -150,6 +150,7 @@ export interface ApplicationStart {
getUrlForApp(appId: string, options?: {
path?: string;
absolute?: boolean;
+ deepLinkId?: string;
}): string;
navigateToApp(appId: string, options?: NavigateToAppOptions): Promise;
navigateToUrl(url: string): Promise;
From 42aa7f569a20b78d1260b8b577f9e4651ca463e0 Mon Sep 17 00:00:00 2001
From: Scotty Bollinger
Date: Tue, 15 Jun 2021 10:12:47 -0500
Subject: [PATCH 55/91] [Enterprise Search] Refactor Role mappings to use
single endpoint (#102096)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Add tooltip back to table row
This was missed when refactoring the table to an EUI component. Built-in mappings have tooltips and don’t have IDs and need to show tooltips instead of actions.
* Fix roleType display
Also missed in the refactor. Made a mistake when copying/pasting
* Refactor logic files to use single endpoint for UI props
As a part of this feature, we are now passing all props needed for the UI in the list endpoint. Previously, whether creating a new mapping, or updating an existing mapping, an endpoint had to be called to fetch the data needed for display. Now all this data comes from the initial fetching of mappings and the other endpoints are no longer needed.
* Refactor WS test to match AS
There was an issue where 100% test coverage was not achieved in Workplace Search. This had already been fixed in App Search by refactoring. This changes the code and test in Workplace Search to match
* Remove server routes
---
.../role_mappings/role_mappings_logic.test.ts | 178 +++++++-----------
.../role_mappings/role_mappings_logic.ts | 62 ++----
.../role_mapping/role_mappings_table.tsx | 19 +-
.../role_mappings/role_mappings_logic.test.ts | 162 ++++++----------
.../role_mappings/role_mappings_logic.ts | 78 +++-----
.../routes/app_search/role_mappings.test.ts | 52 +----
.../server/routes/app_search/role_mappings.ts | 30 ---
.../workplace_search/role_mappings.test.ts | 52 +----
.../routes/workplace_search/role_mappings.ts | 30 ---
9 files changed, 191 insertions(+), 472 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts
index 87e6ee62460fa..870e303a2930d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts
@@ -16,18 +16,13 @@ import { engines } from '../../__mocks__/engines.mock';
import { nextTick } from '@kbn/test/jest';
import { asRoleMapping } from '../../../shared/role_mapping/__mocks__/roles';
-import { ANY_AUTH_PROVIDER, ROLE_MAPPING_NOT_FOUND } from '../../../shared/role_mapping/constants';
+import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants';
import { RoleMappingsLogic } from './role_mappings_logic';
describe('RoleMappingsLogic', () => {
const { http } = mockHttpValues;
- const {
- clearFlashMessages,
- flashAPIErrors,
- setSuccessMessage,
- setErrorMessage,
- } = mockFlashMessageHelpers;
+ const { clearFlashMessages, flashAPIErrors, setSuccessMessage } = mockFlashMessageHelpers;
const { mount } = new LogicMounter(RoleMappingsLogic);
const DEFAULT_VALUES = {
attributes: [],
@@ -50,15 +45,14 @@ describe('RoleMappingsLogic', () => {
roleMappingErrors: [],
};
- const mappingsServerProps = { multipleAuthProvidersConfig: true, roleMappings: [asRoleMapping] };
- const mappingServerProps = {
+ const mappingsServerProps = {
+ multipleAuthProvidersConfig: true,
+ roleMappings: [asRoleMapping],
attributes: ['email', 'metadata', 'username', 'role'],
authProviders: [ANY_AUTH_PROVIDER],
availableEngines: engines,
elasticsearchRoles: [],
hasAdvancedRoles: false,
- multipleAuthProvidersConfig: false,
- roleMapping: asRoleMapping,
};
beforeEach(() => {
@@ -75,48 +69,20 @@ describe('RoleMappingsLogic', () => {
it('sets data based on server response from the `mappings` (plural) endpoint', () => {
RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
- expect(RoleMappingsLogic.values.roleMappings).toEqual([asRoleMapping]);
- expect(RoleMappingsLogic.values.dataLoading).toEqual(false);
- expect(RoleMappingsLogic.values.multipleAuthProvidersConfig).toEqual(true);
- });
- });
-
- describe('setRoleMappingData', () => {
- it('sets state based on server response from the `mapping` (singular) endpoint', () => {
- RoleMappingsLogic.actions.setRoleMappingData(mappingServerProps);
-
expect(RoleMappingsLogic.values).toEqual({
...DEFAULT_VALUES,
- roleMapping: asRoleMapping,
+ roleMappings: [asRoleMapping],
dataLoading: false,
- attributes: mappingServerProps.attributes,
- availableAuthProviders: mappingServerProps.authProviders,
- availableEngines: mappingServerProps.availableEngines,
+ attributes: mappingsServerProps.attributes,
+ availableAuthProviders: mappingsServerProps.authProviders,
+ availableEngines: mappingsServerProps.availableEngines,
accessAllEngines: true,
- attributeName: 'role',
- attributeValue: 'superuser',
- elasticsearchRoles: mappingServerProps.elasticsearchRoles,
- selectedEngines: new Set(engines.map((e) => e.name)),
- selectedOptions: [
- { label: engines[0].name, value: engines[0].name },
- { label: engines[1].name, value: engines[1].name },
- ],
- });
- });
-
- it('will remove all selected engines if no roleMapping was returned from the server', () => {
- RoleMappingsLogic.actions.setRoleMappingData({
- ...mappingServerProps,
- roleMapping: undefined,
- });
-
- expect(RoleMappingsLogic.values).toEqual({
- ...DEFAULT_VALUES,
- dataLoading: false,
+ multipleAuthProvidersConfig: true,
+ attributeName: 'username',
+ attributeValue: '',
+ elasticsearchRoles: mappingsServerProps.elasticsearchRoles,
selectedEngines: new Set(),
- attributes: mappingServerProps.attributes,
- availableAuthProviders: mappingServerProps.authProviders,
- availableEngines: mappingServerProps.availableEngines,
+ selectedOptions: [],
});
});
});
@@ -135,11 +101,13 @@ describe('RoleMappingsLogic', () => {
const engine = engines[0];
const otherEngine = engines[1];
const mountedValues = {
- ...mappingServerProps,
- roleMapping: {
- ...asRoleMapping,
- engines: [engine, otherEngine],
- },
+ ...mappingsServerProps,
+ roleMappings: [
+ {
+ ...asRoleMapping,
+ engines: [engine, otherEngine],
+ },
+ ],
selectedEngines: new Set([engine.name]),
};
@@ -153,11 +121,18 @@ describe('RoleMappingsLogic', () => {
expect(RoleMappingsLogic.values.selectedEngines).toEqual(
new Set([engine.name, otherEngine.name])
);
+ expect(RoleMappingsLogic.values.selectedOptions).toEqual([
+ { label: engine.name, value: engine.name },
+ { label: otherEngine.name, value: otherEngine.name },
+ ]);
});
it('handles removing an engine from selected engines', () => {
RoleMappingsLogic.actions.handleEngineSelectionChange([engine.name]);
expect(RoleMappingsLogic.values.selectedEngines).toEqual(new Set([engine.name]));
+ expect(RoleMappingsLogic.values.selectedOptions).toEqual([
+ { label: engine.name, value: engine.name },
+ ]);
});
});
@@ -175,17 +150,19 @@ describe('RoleMappingsLogic', () => {
it('sets values correctly', () => {
mount({
- ...mappingServerProps,
+ ...mappingsServerProps,
elasticsearchRoles,
});
RoleMappingsLogic.actions.handleAttributeSelectorChange('role', elasticsearchRoles[0]);
expect(RoleMappingsLogic.values).toEqual({
...DEFAULT_VALUES,
+ multipleAuthProvidersConfig: true,
attributeValue: elasticsearchRoles[0],
- roleMapping: asRoleMapping,
- attributes: mappingServerProps.attributes,
- availableEngines: mappingServerProps.availableEngines,
+ roleMappings: [asRoleMapping],
+ roleMapping: null,
+ attributes: mappingsServerProps.attributes,
+ availableEngines: mappingsServerProps.availableEngines,
accessAllEngines: true,
attributeName: 'role',
elasticsearchRoles,
@@ -215,11 +192,13 @@ describe('RoleMappingsLogic', () => {
describe('handleAuthProviderChange', () => {
beforeEach(() => {
mount({
- ...mappingServerProps,
- roleMapping: {
- ...asRoleMapping,
- authProvider: ['foo'],
- },
+ ...mappingsServerProps,
+ roleMappings: [
+ {
+ ...asRoleMapping,
+ authProvider: ['foo'],
+ },
+ ],
});
});
const providers = ['bar', 'baz'];
@@ -244,11 +223,13 @@ describe('RoleMappingsLogic', () => {
it('handles "any" auth in previous state', () => {
mount({
- ...mappingServerProps,
- roleMapping: {
- ...asRoleMapping,
- authProvider: [ANY_AUTH_PROVIDER],
- },
+ ...mappingsServerProps,
+ roleMappings: [
+ {
+ ...asRoleMapping,
+ authProvider: [ANY_AUTH_PROVIDER],
+ },
+ ],
});
RoleMappingsLogic.actions.handleAuthProviderChange(providerWithAny);
@@ -258,7 +239,6 @@ describe('RoleMappingsLogic', () => {
it('resetState', () => {
mount(mappingsServerProps);
- mount(mappingServerProps);
RoleMappingsLogic.actions.resetState();
expect(RoleMappingsLogic.values).toEqual(DEFAULT_VALUES);
@@ -266,7 +246,7 @@ describe('RoleMappingsLogic', () => {
});
it('openRoleMappingFlyout', () => {
- mount(mappingServerProps);
+ mount(mappingsServerProps);
RoleMappingsLogic.actions.openRoleMappingFlyout();
expect(RoleMappingsLogic.values.roleMappingFlyoutOpen).toEqual(true);
@@ -275,7 +255,7 @@ describe('RoleMappingsLogic', () => {
it('closeRoleMappingFlyout', () => {
mount({
- ...mappingServerProps,
+ ...mappingsServerProps,
roleMappingFlyoutOpen: true,
});
RoleMappingsLogic.actions.closeRoleMappingFlyout();
@@ -307,40 +287,20 @@ describe('RoleMappingsLogic', () => {
});
describe('initializeRoleMapping', () => {
- it('calls API and sets values for new mapping', async () => {
- const setRoleMappingDataSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMappingData');
- http.get.mockReturnValue(Promise.resolve(mappingServerProps));
- RoleMappingsLogic.actions.initializeRoleMapping();
-
- expect(http.get).toHaveBeenCalledWith('/api/app_search/role_mappings/new');
- await nextTick();
- expect(setRoleMappingDataSpy).toHaveBeenCalledWith(mappingServerProps);
- });
-
- it('calls API and sets values for existing mapping', async () => {
- const setRoleMappingDataSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMappingData');
- http.get.mockReturnValue(Promise.resolve(mappingServerProps));
- RoleMappingsLogic.actions.initializeRoleMapping('123');
-
- expect(http.get).toHaveBeenCalledWith('/api/app_search/role_mappings/123');
- await nextTick();
- expect(setRoleMappingDataSpy).toHaveBeenCalledWith(mappingServerProps);
- });
-
- it('handles error', async () => {
- http.get.mockReturnValue(Promise.reject('this is an error'));
- RoleMappingsLogic.actions.initializeRoleMapping();
- await nextTick();
+ it('sets values for existing mapping', () => {
+ const setRoleMappingDataSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMapping');
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
+ RoleMappingsLogic.actions.initializeRoleMapping(asRoleMapping.id);
- expect(flashAPIErrors).toHaveBeenCalledWith('this is an error');
+ expect(setRoleMappingDataSpy).toHaveBeenCalledWith(asRoleMapping);
});
- it('shows error when there is a 404 status', async () => {
- http.get.mockReturnValue(Promise.reject({ status: 404 }));
+ it('does not set data for new mapping', async () => {
+ const setRoleMappingDataSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMapping');
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
RoleMappingsLogic.actions.initializeRoleMapping();
- await nextTick();
- expect(setErrorMessage).toHaveBeenCalledWith(ROLE_MAPPING_NOT_FOUND);
+ expect(setRoleMappingDataSpy).not.toHaveBeenCalledWith(asRoleMapping);
});
});
@@ -362,7 +322,7 @@ describe('RoleMappingsLogic', () => {
'initializeRoleMappings'
);
- http.post.mockReturnValue(Promise.resolve(mappingServerProps));
+ http.post.mockReturnValue(Promise.resolve(mappingsServerProps));
RoleMappingsLogic.actions.handleSaveMapping();
expect(http.post).toHaveBeenCalledWith('/api/app_search/role_mappings', {
@@ -374,13 +334,16 @@ describe('RoleMappingsLogic', () => {
});
it('calls API and refreshes list when existing mapping', async () => {
- mount(mappingServerProps);
+ mount({
+ ...mappingsServerProps,
+ roleMapping: asRoleMapping,
+ });
const initializeRoleMappingsSpy = jest.spyOn(
RoleMappingsLogic.actions,
'initializeRoleMappings'
);
- http.put.mockReturnValue(Promise.resolve(mappingServerProps));
+ http.put.mockReturnValue(Promise.resolve(mappingsServerProps));
RoleMappingsLogic.actions.handleSaveMapping();
expect(http.put).toHaveBeenCalledWith(`/api/app_search/role_mappings/${asRoleMapping.id}`, {
@@ -396,12 +359,13 @@ describe('RoleMappingsLogic', () => {
const engine = engines[0];
mount({
- ...mappingServerProps,
+ ...mappingsServerProps,
accessAllEngines: false,
selectedEngines: new Set([engine.name]),
+ roleMapping: asRoleMapping,
});
- http.put.mockReturnValue(Promise.resolve(mappingServerProps));
+ http.put.mockReturnValue(Promise.resolve(mappingsServerProps));
RoleMappingsLogic.actions.handleSaveMapping();
expect(http.put).toHaveBeenCalledWith(`/api/app_search/role_mappings/${asRoleMapping.id}`, {
@@ -449,7 +413,7 @@ describe('RoleMappingsLogic', () => {
});
it('calls API and refreshes list', async () => {
- mount(mappingServerProps);
+ mount(mappingsServerProps);
const initializeRoleMappingsSpy = jest.spyOn(
RoleMappingsLogic.actions,
'initializeRoleMappings'
@@ -465,7 +429,7 @@ describe('RoleMappingsLogic', () => {
});
it('handles error', async () => {
- mount(mappingServerProps);
+ mount(mappingsServerProps);
http.delete.mockReturnValue(Promise.reject('this is an error'));
RoleMappingsLogic.actions.handleDeleteMapping(roleMappingId);
await nextTick();
@@ -474,7 +438,7 @@ describe('RoleMappingsLogic', () => {
});
it('will do nothing if not confirmed', () => {
- mount(mappingServerProps);
+ mount(mappingsServerProps);
jest.spyOn(window, 'confirm').mockReturnValueOnce(false);
RoleMappingsLogic.actions.handleDeleteMapping(roleMappingId);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts
index 863e6746dbe95..fc0a235b23c77 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts
@@ -13,10 +13,9 @@ import {
clearFlashMessages,
flashAPIErrors,
setSuccessMessage,
- setErrorMessage,
} from '../../../shared/flash_messages';
import { HttpLogic } from '../../../shared/http';
-import { ANY_AUTH_PROVIDER, ROLE_MAPPING_NOT_FOUND } from '../../../shared/role_mapping/constants';
+import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants';
import { AttributeName } from '../../../shared/types';
import { ASRoleMapping, RoleTypes } from '../../types';
import { roleHasScopedEngines } from '../../utils/role/has_scoped_engines';
@@ -31,17 +30,12 @@ import {
interface RoleMappingsServerDetails {
roleMappings: ASRoleMapping[];
- multipleAuthProvidersConfig: boolean;
-}
-
-interface RoleMappingServerDetails {
attributes: string[];
authProviders: string[];
availableEngines: Engine[];
elasticsearchRoles: string[];
hasAdvancedRoles: boolean;
multipleAuthProvidersConfig: boolean;
- roleMapping?: ASRoleMapping;
}
const getFirstAttributeName = (roleMapping: ASRoleMapping) =>
@@ -64,7 +58,7 @@ interface RoleMappingsActions {
initializeRoleMapping(roleMappingId?: string): { roleMappingId?: string };
initializeRoleMappings(): void;
resetState(): void;
- setRoleMappingData(data: RoleMappingServerDetails): RoleMappingServerDetails;
+ setRoleMapping(roleMapping: ASRoleMapping): { roleMapping: ASRoleMapping };
setRoleMappingsData(data: RoleMappingsServerDetails): RoleMappingsServerDetails;
openRoleMappingFlyout(): void;
closeRoleMappingFlyout(): void;
@@ -96,7 +90,7 @@ export const RoleMappingsLogic = kea data,
- setRoleMappingData: (data: RoleMappingServerDetails) => data,
+ setRoleMapping: (roleMapping: ASRoleMapping) => ({ roleMapping }),
setRoleMappingErrors: (errors: string[]) => ({ errors }),
handleAuthProviderChange: (value: string) => ({ value }),
handleRoleChange: (roleType: RoleTypes) => ({ roleType }),
@@ -120,7 +114,6 @@ export const RoleMappingsLogic = kea false,
- setRoleMappingData: () => false,
resetState: () => true,
},
],
@@ -135,40 +128,39 @@ export const RoleMappingsLogic = kea multipleAuthProvidersConfig,
- setRoleMappingData: (_, { multipleAuthProvidersConfig }) => multipleAuthProvidersConfig,
resetState: () => false,
},
],
hasAdvancedRoles: [
false,
{
- setRoleMappingData: (_, { hasAdvancedRoles }) => hasAdvancedRoles,
+ setRoleMappingsData: (_, { hasAdvancedRoles }) => hasAdvancedRoles,
},
],
availableEngines: [
[],
{
- setRoleMappingData: (_, { availableEngines }) => availableEngines,
+ setRoleMappingsData: (_, { availableEngines }) => availableEngines,
resetState: () => [],
},
],
attributes: [
[],
{
- setRoleMappingData: (_, { attributes }) => attributes,
+ setRoleMappingsData: (_, { attributes }) => attributes,
resetState: () => [],
},
],
elasticsearchRoles: [
[],
{
- setRoleMappingData: (_, { elasticsearchRoles }) => elasticsearchRoles,
+ setRoleMappingsData: (_, { elasticsearchRoles }) => elasticsearchRoles,
},
],
roleMapping: [
null,
{
- setRoleMappingData: (_, { roleMapping }) => roleMapping || null,
+ setRoleMapping: (_, { roleMapping }) => roleMapping,
resetState: () => null,
closeRoleMappingFlyout: () => null,
},
@@ -176,16 +168,14 @@ export const RoleMappingsLogic = kea
- roleMapping ? (roleMapping.roleType as RoleTypes) : 'owner',
+ setRoleMapping: (_, { roleMapping }) => roleMapping.roleType as RoleTypes,
handleRoleChange: (_, { roleType }) => roleType,
},
],
accessAllEngines: [
true,
{
- setRoleMappingData: (_, { roleMapping }) =>
- roleMapping ? roleMapping.accessAllEngines : true,
+ setRoleMapping: (_, { roleMapping }) => roleMapping.accessAllEngines,
handleRoleChange: (_, { roleType }) => !roleHasScopedEngines(roleType),
handleAccessAllEnginesChange: (_, { selected }) => selected,
},
@@ -193,8 +183,7 @@ export const RoleMappingsLogic = kea
- roleMapping ? getFirstAttributeValue(roleMapping) : '',
+ setRoleMapping: (_, { roleMapping }) => getFirstAttributeValue(roleMapping),
handleAttributeSelectorChange: (_, { value, firstElasticsearchRole }) =>
value === 'role' ? firstElasticsearchRole : '',
handleAttributeValueChange: (_, { value }) => value,
@@ -205,8 +194,7 @@ export const RoleMappingsLogic = kea
- roleMapping ? getFirstAttributeName(roleMapping) : 'username',
+ setRoleMapping: (_, { roleMapping }) => getFirstAttributeName(roleMapping),
handleAttributeSelectorChange: (_, { value }) => value,
resetState: () => 'username',
closeRoleMappingFlyout: () => 'username',
@@ -215,8 +203,8 @@ export const RoleMappingsLogic = kea
- roleMapping ? new Set(roleMapping.engines.map((engine) => engine.name)) : new Set(),
+ setRoleMapping: (_, { roleMapping }) =>
+ new Set(roleMapping.engines.map((engine: Engine) => engine.name)),
handleAccessAllEnginesChange: () => new Set(),
handleEngineSelectionChange: (_, { engineNames }) => {
const newSelectedEngineNames = new Set() as Set;
@@ -229,7 +217,7 @@ export const RoleMappingsLogic = kea authProviders,
+ setRoleMappingsData: (_, { authProviders }) => authProviders,
},
],
selectedAuthProviders: [
@@ -246,8 +234,7 @@ export const RoleMappingsLogic = kea v !== ANY_AUTH_PROVIDER);
return [ANY_AUTH_PROVIDER];
},
- setRoleMappingData: (_, { roleMapping }) =>
- roleMapping ? roleMapping.authProvider : [ANY_AUTH_PROVIDER],
+ setRoleMapping: (_, { roleMapping }) => roleMapping.authProvider,
},
],
roleMappingFlyoutOpen: [
@@ -292,21 +279,8 @@ export const RoleMappingsLogic = kea {
- const { http } = HttpLogic.values;
- const route = roleMappingId
- ? `/api/app_search/role_mappings/${roleMappingId}`
- : '/api/app_search/role_mappings/new';
-
- try {
- const response = await http.get(route);
- actions.setRoleMappingData(response);
- } catch (e) {
- if (e.status === 404) {
- setErrorMessage(ROLE_MAPPING_NOT_FOUND);
- } else {
- flashAPIErrors(e);
- }
- }
+ const roleMapping = values.roleMappings.find(({ id }) => id === roleMappingId);
+ if (roleMapping) actions.setRoleMapping(roleMapping);
},
handleDeleteMapping: async ({ roleMappingId }) => {
const { http } = HttpLogic.values;
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx
index 82284be0907fb..7696cf03ed4b1 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx
@@ -7,7 +7,7 @@
import React, { Fragment } from 'react';
-import { EuiTextColor, EuiInMemoryTable, EuiBasicTableColumn } from '@elastic/eui';
+import { EuiIconTip, EuiTextColor, EuiInMemoryTable, EuiBasicTableColumn } from '@elastic/eui';
import { ASRoleMapping } from '../../app_search/types';
import { WSRoleMapping } from '../../workplace_search/types';
@@ -84,7 +84,7 @@ export const RoleMappingsTable: React.FC = ({
const roleCol: EuiBasicTableColumn = {
field: 'roleType',
name: ROLE_LABEL,
- render: (_, { rules }: SharedRoleMapping) => getFirstAttributeValue(rules),
+ render: (_, { roleType }: SharedRoleMapping) => roleType,
};
const accessItemsCol: EuiBasicTableColumn = {
@@ -124,11 +124,16 @@ export const RoleMappingsTable: React.FC = ({
field: 'id',
name: '',
align: 'right',
- render: (_, { id }: SharedRoleMapping) => (
- initializeRoleMapping(id)}
- onDeleteClick={() => handleDeleteMapping(id)}
- />
+ render: (_, { id, toolTip }: SharedRoleMapping) => (
+ <>
+ {id && (
+ initializeRoleMapping(id)}
+ onDeleteClick={() => handleDeleteMapping(id)}
+ />
+ )}
+ {toolTip && }
+ >
),
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts
index 716cb8ebb6d47..4ee530870284e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts
@@ -16,13 +16,13 @@ import { groups } from '../../__mocks__/groups.mock';
import { nextTick } from '@kbn/test/jest';
import { wsRoleMapping } from '../../../shared/role_mapping/__mocks__/roles';
-import { ANY_AUTH_PROVIDER, ROLE_MAPPING_NOT_FOUND } from '../../../shared/role_mapping/constants';
+import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants';
import { RoleMappingsLogic } from './role_mappings_logic';
describe('RoleMappingsLogic', () => {
const { http } = mockHttpValues;
- const { clearFlashMessages, flashAPIErrors, setErrorMessage } = mockFlashMessageHelpers;
+ const { clearFlashMessages, flashAPIErrors } = mockFlashMessageHelpers;
const { mount } = new LogicMounter(RoleMappingsLogic);
const defaultValues = {
attributes: [],
@@ -52,14 +52,13 @@ describe('RoleMappingsLogic', () => {
name: 'Default',
};
- const mappingsServerProps = { multipleAuthProvidersConfig: true, roleMappings: [wsRoleMapping] };
- const mappingServerProps = {
+ const mappingsServerProps = {
+ multipleAuthProvidersConfig: true,
+ roleMappings: [wsRoleMapping],
attributes: [],
authProviders: [],
availableGroups: [roleGroup, defaultGroup],
elasticsearchRoles: [],
- multipleAuthProvidersConfig: false,
- roleMapping: wsRoleMapping,
};
beforeEach(() => {
@@ -78,36 +77,17 @@ describe('RoleMappingsLogic', () => {
expect(RoleMappingsLogic.values.roleMappings).toEqual([wsRoleMapping]);
expect(RoleMappingsLogic.values.dataLoading).toEqual(false);
expect(RoleMappingsLogic.values.multipleAuthProvidersConfig).toEqual(true);
- });
-
- describe('setRoleMappingData', () => {
- it('sets data correctly', () => {
- RoleMappingsLogic.actions.setRoleMappingData(mappingServerProps);
-
- expect(RoleMappingsLogic.values.roleMapping).toEqual(wsRoleMapping);
- expect(RoleMappingsLogic.values.dataLoading).toEqual(false);
- expect(RoleMappingsLogic.values.attributes).toEqual(mappingServerProps.attributes);
- expect(RoleMappingsLogic.values.availableGroups).toEqual(
- mappingServerProps.availableGroups
- );
- expect(RoleMappingsLogic.values.includeInAllGroups).toEqual(true);
- expect(RoleMappingsLogic.values.elasticsearchRoles).toEqual(
- mappingServerProps.elasticsearchRoles
- );
- expect(RoleMappingsLogic.values.selectedGroups).toEqual(
- new Set([wsRoleMapping.groups[0].id])
- );
- expect(RoleMappingsLogic.values.selectedOptions).toEqual([]);
- });
-
- it('sets default group with new role mapping', () => {
- RoleMappingsLogic.actions.setRoleMappingData({
- ...mappingServerProps,
- roleMapping: undefined,
- });
-
- expect(RoleMappingsLogic.values.selectedGroups).toEqual(new Set([defaultGroup.id]));
- });
+ expect(RoleMappingsLogic.values.dataLoading).toEqual(false);
+ expect(RoleMappingsLogic.values.attributes).toEqual(mappingsServerProps.attributes);
+ expect(RoleMappingsLogic.values.availableGroups).toEqual(mappingsServerProps.availableGroups);
+ expect(RoleMappingsLogic.values.includeInAllGroups).toEqual(false);
+ expect(RoleMappingsLogic.values.elasticsearchRoles).toEqual(
+ mappingsServerProps.elasticsearchRoles
+ );
+ expect(RoleMappingsLogic.values.selectedOptions).toEqual([
+ { label: defaultGroup.name, value: defaultGroup.id },
+ ]);
+ expect(RoleMappingsLogic.values.selectedGroups).toEqual(new Set([defaultGroup.id]));
});
it('handleRoleChange', () => {
@@ -119,14 +99,17 @@ describe('RoleMappingsLogic', () => {
it('handleGroupSelectionChange', () => {
const group = wsRoleMapping.groups[0];
const otherGroup = groups[0];
- RoleMappingsLogic.actions.setRoleMappingData({
- ...mappingServerProps,
- roleMapping: {
- ...wsRoleMapping,
- groups: [group, otherGroup],
- },
+ RoleMappingsLogic.actions.setRoleMappingsData({
+ ...mappingsServerProps,
+ roleMappings: [
+ {
+ ...wsRoleMapping,
+ groups: [group, otherGroup],
+ },
+ ],
});
+ RoleMappingsLogic.actions.initializeRoleMapping(wsRoleMapping.id);
RoleMappingsLogic.actions.handleGroupSelectionChange([group.id, otherGroup.id]);
expect(RoleMappingsLogic.values.selectedGroups).toEqual(new Set([group.id, otherGroup.id]));
expect(RoleMappingsLogic.values.selectedOptions).toEqual([
@@ -147,8 +130,8 @@ describe('RoleMappingsLogic', () => {
const elasticsearchRoles = ['foo', 'bar'];
it('sets values correctly', () => {
- RoleMappingsLogic.actions.setRoleMappingData({
- ...mappingServerProps,
+ RoleMappingsLogic.actions.setRoleMappingsData({
+ ...mappingsServerProps,
elasticsearchRoles,
});
RoleMappingsLogic.actions.handleAttributeSelectorChange('role', elasticsearchRoles[0]);
@@ -172,12 +155,14 @@ describe('RoleMappingsLogic', () => {
describe('handleAuthProviderChange', () => {
beforeEach(() => {
- RoleMappingsLogic.actions.setRoleMappingData({
- ...mappingServerProps,
- roleMapping: {
- ...wsRoleMapping,
- authProvider: ['foo'],
- },
+ RoleMappingsLogic.actions.setRoleMappingsData({
+ ...mappingsServerProps,
+ roleMappings: [
+ {
+ ...wsRoleMapping,
+ authProvider: ['foo'],
+ },
+ ],
});
});
const providers = ['bar', 'baz'];
@@ -201,28 +186,23 @@ describe('RoleMappingsLogic', () => {
});
it('handles "any" auth in previous state', () => {
- RoleMappingsLogic.actions.setRoleMappingData({
- ...mappingServerProps,
- roleMapping: {
- ...wsRoleMapping,
- authProvider: [ANY_AUTH_PROVIDER],
- },
+ RoleMappingsLogic.actions.setRoleMappingsData({
+ ...mappingsServerProps,
+ roleMappings: [
+ {
+ ...wsRoleMapping,
+ authProvider: [ANY_AUTH_PROVIDER],
+ },
+ ],
});
RoleMappingsLogic.actions.handleAuthProviderChange(providerWithAny);
expect(RoleMappingsLogic.values.selectedAuthProviders).toEqual([providers[1]]);
});
-
- it('handles catch-all state', () => {
- RoleMappingsLogic.actions.handleAuthProviderChange(providerWithAny);
-
- expect(RoleMappingsLogic.values.selectedAuthProviders).toEqual([ANY_AUTH_PROVIDER]);
- });
});
it('resetState', () => {
RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
- RoleMappingsLogic.actions.setRoleMappingData(mappingServerProps);
RoleMappingsLogic.actions.resetState();
expect(RoleMappingsLogic.values.dataLoading).toEqual(true);
@@ -234,7 +214,7 @@ describe('RoleMappingsLogic', () => {
});
it('openRoleMappingFlyout', () => {
- mount(mappingServerProps);
+ mount(mappingsServerProps);
RoleMappingsLogic.actions.openRoleMappingFlyout();
expect(RoleMappingsLogic.values.roleMappingFlyoutOpen).toEqual(true);
@@ -243,7 +223,7 @@ describe('RoleMappingsLogic', () => {
it('closeRoleMappingFlyout', () => {
mount({
- ...mappingServerProps,
+ ...mappingsServerProps,
roleMappingFlyoutOpen: true,
});
RoleMappingsLogic.actions.closeRoleMappingFlyout();
@@ -275,40 +255,20 @@ describe('RoleMappingsLogic', () => {
});
describe('initializeRoleMapping', () => {
- it('calls API and sets values for new mapping', async () => {
- const setRoleMappingDataSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMappingData');
- http.get.mockReturnValue(Promise.resolve(mappingServerProps));
- RoleMappingsLogic.actions.initializeRoleMapping();
-
- expect(http.get).toHaveBeenCalledWith('/api/workplace_search/org/role_mappings/new');
- await nextTick();
- expect(setRoleMappingDataSpy).toHaveBeenCalledWith(mappingServerProps);
- });
-
- it('calls API and sets values for existing mapping', async () => {
- const setRoleMappingDataSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMappingData');
- http.get.mockReturnValue(Promise.resolve(mappingServerProps));
- RoleMappingsLogic.actions.initializeRoleMapping('123');
-
- expect(http.get).toHaveBeenCalledWith('/api/workplace_search/org/role_mappings/123');
- await nextTick();
- expect(setRoleMappingDataSpy).toHaveBeenCalledWith(mappingServerProps);
- });
-
- it('handles error', async () => {
- http.get.mockReturnValue(Promise.reject('this is an error'));
- RoleMappingsLogic.actions.initializeRoleMapping();
- await nextTick();
+ it('sets values for existing mapping', () => {
+ const setRoleMappingDataSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMapping');
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
+ RoleMappingsLogic.actions.initializeRoleMapping(wsRoleMapping.id);
- expect(flashAPIErrors).toHaveBeenCalledWith('this is an error');
+ expect(setRoleMappingDataSpy).toHaveBeenCalledWith(wsRoleMapping);
});
- it('shows error when there is a 404 status', async () => {
- http.get.mockReturnValue(Promise.reject({ status: 404 }));
+ it('does not set data for new mapping', async () => {
+ const setRoleMappingDataSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMapping');
+ RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
RoleMappingsLogic.actions.initializeRoleMapping();
- await nextTick();
- expect(setErrorMessage).toHaveBeenCalledWith(ROLE_MAPPING_NOT_FOUND);
+ expect(setRoleMappingDataSpy).not.toHaveBeenCalledWith(wsRoleMapping);
});
});
@@ -320,7 +280,7 @@ describe('RoleMappingsLogic', () => {
);
RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps);
- http.post.mockReturnValue(Promise.resolve(mappingServerProps));
+ http.post.mockReturnValue(Promise.resolve(mappingsServerProps));
RoleMappingsLogic.actions.handleSaveMapping();
expect(http.post).toHaveBeenCalledWith('/api/workplace_search/org/role_mappings', {
@@ -331,7 +291,7 @@ describe('RoleMappingsLogic', () => {
rules: {
username: '',
},
- groups: [],
+ groups: [defaultGroup.id],
}),
});
await nextTick();
@@ -344,9 +304,9 @@ describe('RoleMappingsLogic', () => {
RoleMappingsLogic.actions,
'initializeRoleMappings'
);
- RoleMappingsLogic.actions.setRoleMappingData(mappingServerProps);
+ RoleMappingsLogic.actions.setRoleMapping(wsRoleMapping);
- http.put.mockReturnValue(Promise.resolve(mappingServerProps));
+ http.put.mockReturnValue(Promise.resolve(mappingsServerProps));
RoleMappingsLogic.actions.handleSaveMapping();
expect(http.put).toHaveBeenCalledWith(
@@ -408,7 +368,7 @@ describe('RoleMappingsLogic', () => {
RoleMappingsLogic.actions,
'initializeRoleMappings'
);
- RoleMappingsLogic.actions.setRoleMappingData(mappingServerProps);
+ RoleMappingsLogic.actions.setRoleMapping(wsRoleMapping);
http.delete.mockReturnValue(Promise.resolve({}));
RoleMappingsLogic.actions.handleDeleteMapping(roleMappingId);
@@ -421,7 +381,7 @@ describe('RoleMappingsLogic', () => {
});
it('handles error', async () => {
- RoleMappingsLogic.actions.setRoleMappingData(mappingServerProps);
+ RoleMappingsLogic.actions.setRoleMapping(wsRoleMapping);
http.delete.mockReturnValue(Promise.reject('this is an error'));
RoleMappingsLogic.actions.handleDeleteMapping(roleMappingId);
await nextTick();
@@ -430,7 +390,7 @@ describe('RoleMappingsLogic', () => {
});
it('will do nothing if not confirmed', async () => {
- RoleMappingsLogic.actions.setRoleMappingData(mappingServerProps);
+ RoleMappingsLogic.actions.setRoleMapping(wsRoleMapping);
window.confirm = () => false;
RoleMappingsLogic.actions.handleDeleteMapping(roleMappingId);
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts
index aee780ac18971..361425b7a78a1 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts
@@ -13,10 +13,9 @@ import {
clearFlashMessages,
flashAPIErrors,
setSuccessMessage,
- setErrorMessage,
} from '../../../shared/flash_messages';
import { HttpLogic } from '../../../shared/http';
-import { ANY_AUTH_PROVIDER, ROLE_MAPPING_NOT_FOUND } from '../../../shared/role_mapping/constants';
+import { ANY_AUTH_PROVIDER } from '../../../shared/role_mapping/constants';
import { AttributeName } from '../../../shared/types';
import { RoleGroup, WSRoleMapping, Role } from '../../types';
@@ -30,16 +29,11 @@ import {
interface RoleMappingsServerDetails {
roleMappings: WSRoleMapping[];
- multipleAuthProvidersConfig: boolean;
-}
-
-interface RoleMappingServerDetails {
attributes: string[];
authProviders: string[];
availableGroups: RoleGroup[];
elasticsearchRoles: string[];
multipleAuthProvidersConfig: boolean;
- roleMapping?: WSRoleMapping;
}
const getFirstAttributeName = (roleMapping: WSRoleMapping): AttributeName =>
@@ -62,7 +56,7 @@ interface RoleMappingsActions {
initializeRoleMapping(roleMappingId?: string): { roleMappingId?: string };
initializeRoleMappings(): void;
resetState(): void;
- setRoleMappingData(data: RoleMappingServerDetails): RoleMappingServerDetails;
+ setRoleMapping(roleMapping: WSRoleMapping): { roleMapping: WSRoleMapping };
setRoleMappingsData(data: RoleMappingsServerDetails): RoleMappingsServerDetails;
openRoleMappingFlyout(): void;
closeRoleMappingFlyout(): void;
@@ -93,7 +87,7 @@ export const RoleMappingsLogic = kea data,
- setRoleMappingData: (data: RoleMappingServerDetails) => data,
+ setRoleMapping: (roleMapping: WSRoleMapping) => ({ roleMapping }),
setRoleMappingErrors: (errors: string[]) => ({ errors }),
handleAuthProviderChange: (value: string[]) => ({ value }),
handleRoleChange: (roleType: Role) => ({ roleType }),
@@ -117,7 +111,6 @@ export const RoleMappingsLogic = kea false,
- setRoleMappingData: () => false,
resetState: () => true,
},
],
@@ -132,32 +125,31 @@ export const RoleMappingsLogic = kea multipleAuthProvidersConfig,
- setRoleMappingData: (_, { multipleAuthProvidersConfig }) => multipleAuthProvidersConfig,
resetState: () => false,
},
],
availableGroups: [
[],
{
- setRoleMappingData: (_, { availableGroups }) => availableGroups,
+ setRoleMappingsData: (_, { availableGroups }) => availableGroups,
},
],
attributes: [
[],
{
- setRoleMappingData: (_, { attributes }) => attributes,
+ setRoleMappingsData: (_, { attributes }) => attributes,
},
],
elasticsearchRoles: [
[],
{
- setRoleMappingData: (_, { elasticsearchRoles }) => elasticsearchRoles,
+ setRoleMappingsData: (_, { elasticsearchRoles }) => elasticsearchRoles,
},
],
roleMapping: [
null,
{
- setRoleMappingData: (_, { roleMapping }) => roleMapping || null,
+ setRoleMapping: (_, { roleMapping }) => roleMapping,
resetState: () => null,
closeRoleMappingFlyout: () => null,
},
@@ -165,23 +157,21 @@ export const RoleMappingsLogic = kea
- roleMapping ? (roleMapping.roleType as Role) : 'admin',
+ setRoleMapping: (_, { roleMapping }) => roleMapping.roleType as Role,
handleRoleChange: (_, { roleType }) => roleType,
},
],
includeInAllGroups: [
false,
{
- setRoleMappingData: (_, { roleMapping }) => (roleMapping ? roleMapping.allGroups : false),
+ setRoleMapping: (_, { roleMapping }) => roleMapping.allGroups,
handleAllGroupsSelectionChange: (_, { selected }) => selected,
},
],
attributeValue: [
'',
{
- setRoleMappingData: (_, { roleMapping }) =>
- roleMapping ? getFirstAttributeValue(roleMapping) : '',
+ setRoleMapping: (_, { roleMapping }) => getFirstAttributeValue(roleMapping),
handleAttributeSelectorChange: (_, { value, firstElasticsearchRole }) =>
value === 'role' ? firstElasticsearchRole : '',
handleAttributeValueChange: (_, { value }) => value,
@@ -192,8 +182,7 @@ export const RoleMappingsLogic = kea
- roleMapping ? getFirstAttributeName(roleMapping) : 'username',
+ setRoleMapping: (_, { roleMapping }) => getFirstAttributeName(roleMapping),
handleAttributeSelectorChange: (_, { value }) => value,
resetState: () => 'username',
closeRoleMappingFlyout: () => 'username',
@@ -202,14 +191,14 @@ export const RoleMappingsLogic = kea
- roleMapping
- ? new Set(roleMapping.groups.map((group) => group.id))
- : new Set(
- availableGroups
- .filter((group) => group.name === DEFAULT_GROUP_NAME)
- .map((group) => group.id)
- ),
+ setRoleMappingsData: (_, { availableGroups }) =>
+ new Set(
+ availableGroups
+ .filter((group) => group.name === DEFAULT_GROUP_NAME)
+ .map((group) => group.id)
+ ),
+ setRoleMapping: (_, { roleMapping }) =>
+ new Set(roleMapping.groups.map((group: RoleGroup) => group.id)),
handleGroupSelectionChange: (_, { groupIds }) => {
const newSelectedGroupNames = new Set() as Set;
groupIds.forEach((groupId) => newSelectedGroupNames.add(groupId));
@@ -221,7 +210,7 @@ export const RoleMappingsLogic = kea authProviders,
+ setRoleMappingsData: (_, { authProviders }) => authProviders,
},
],
selectedAuthProviders: [
@@ -230,15 +219,15 @@ export const RoleMappingsLogic = kea {
const previouslyContainedAny = previous.includes(ANY_AUTH_PROVIDER);
const newSelectionsContainAny = value.includes(ANY_AUTH_PROVIDER);
+ const hasItems = value.length > 0;
- if (value.length < 1) return [ANY_AUTH_PROVIDER];
if (value.length === 1) return value;
- if (!newSelectionsContainAny) return value;
- if (previouslyContainedAny) return value.filter((v) => v !== ANY_AUTH_PROVIDER);
+ if (!newSelectionsContainAny && hasItems) return value;
+ if (previouslyContainedAny && hasItems)
+ return value.filter((v) => v !== ANY_AUTH_PROVIDER);
return [ANY_AUTH_PROVIDER];
},
- setRoleMappingData: (_, { roleMapping }) =>
- roleMapping ? roleMapping.authProvider : [ANY_AUTH_PROVIDER],
+ setRoleMapping: (_, { roleMapping }) => roleMapping.authProvider,
},
],
roleMappingFlyoutOpen: [
@@ -283,21 +272,8 @@ export const RoleMappingsLogic = kea {
- const { http } = HttpLogic.values;
- const route = roleMappingId
- ? `/api/workplace_search/org/role_mappings/${roleMappingId}`
- : '/api/workplace_search/org/role_mappings/new';
-
- try {
- const response = await http.get(route);
- actions.setRoleMappingData(response);
- } catch (e) {
- if (e.status === 404) {
- setErrorMessage(ROLE_MAPPING_NOT_FOUND);
- } else {
- flashAPIErrors(e);
- }
- }
+ const roleMapping = values.roleMappings.find(({ id }) => id === roleMappingId);
+ if (roleMapping) actions.setRoleMapping(roleMapping);
},
handleDeleteMapping: async ({ roleMappingId }) => {
const { http } = HttpLogic.values;
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts
index a126d06f303b4..718597c12e9c5 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.test.ts
@@ -7,11 +7,7 @@
import { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__';
-import {
- registerRoleMappingsRoute,
- registerRoleMappingRoute,
- registerNewRoleMappingRoute,
-} from './role_mappings';
+import { registerRoleMappingsRoute, registerRoleMappingRoute } from './role_mappings';
const roleMappingBaseSchema = {
rules: { username: 'user' },
@@ -80,29 +76,6 @@ describe('role mappings routes', () => {
});
});
- describe('GET /api/app_search/role_mappings/{id}', () => {
- let mockRouter: MockRouter;
-
- beforeEach(() => {
- jest.clearAllMocks();
- mockRouter = new MockRouter({
- method: 'get',
- path: '/api/app_search/role_mappings/{id}',
- });
-
- registerRoleMappingRoute({
- ...mockDependencies,
- router: mockRouter.router,
- });
- });
-
- it('creates a request handler', () => {
- expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
- path: '/role_mappings/:id',
- });
- });
- });
-
describe('PUT /api/app_search/role_mappings/{id}', () => {
let mockRouter: MockRouter;
@@ -160,27 +133,4 @@ describe('role mappings routes', () => {
});
});
});
-
- describe('GET /api/app_search/role_mappings/new', () => {
- let mockRouter: MockRouter;
-
- beforeEach(() => {
- jest.clearAllMocks();
- mockRouter = new MockRouter({
- method: 'get',
- path: '/api/app_search/role_mappings/new',
- });
-
- registerNewRoleMappingRoute({
- ...mockDependencies,
- router: mockRouter.router,
- });
- });
-
- it('creates a request handler', () => {
- expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
- path: '/role_mappings/new',
- });
- });
- });
});
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts
index 86e17b575e019..75724a3344d6d 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/role_mappings.ts
@@ -48,20 +48,6 @@ export function registerRoleMappingRoute({
router,
enterpriseSearchRequestHandler,
}: RouteDependencies) {
- router.get(
- {
- path: '/api/app_search/role_mappings/{id}',
- validate: {
- params: schema.object({
- id: schema.string(),
- }),
- },
- },
- enterpriseSearchRequestHandler.createRequest({
- path: '/role_mappings/:id',
- })
- );
-
router.put(
{
path: '/api/app_search/role_mappings/{id}',
@@ -92,23 +78,7 @@ export function registerRoleMappingRoute({
);
}
-export function registerNewRoleMappingRoute({
- router,
- enterpriseSearchRequestHandler,
-}: RouteDependencies) {
- router.get(
- {
- path: '/api/app_search/role_mappings/new',
- validate: false,
- },
- enterpriseSearchRequestHandler.createRequest({
- path: '/role_mappings/new',
- })
- );
-}
-
export const registerRoleMappingsRoutes = (dependencies: RouteDependencies) => {
registerRoleMappingsRoute(dependencies);
registerRoleMappingRoute(dependencies);
- registerNewRoleMappingRoute(dependencies);
};
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.test.ts
index 0dade134767e4..a945866da5ef2 100644
--- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.test.ts
@@ -7,11 +7,7 @@
import { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__';
-import {
- registerOrgRoleMappingsRoute,
- registerOrgRoleMappingRoute,
- registerOrgNewRoleMappingRoute,
-} from './role_mappings';
+import { registerOrgRoleMappingsRoute, registerOrgRoleMappingRoute } from './role_mappings';
describe('role mappings routes', () => {
describe('GET /api/workplace_search/org/role_mappings', () => {
@@ -60,29 +56,6 @@ describe('role mappings routes', () => {
});
});
- describe('GET /api/workplace_search/org/role_mappings/{id}', () => {
- let mockRouter: MockRouter;
-
- beforeEach(() => {
- jest.clearAllMocks();
- mockRouter = new MockRouter({
- method: 'get',
- path: '/api/workplace_search/org/role_mappings/{id}',
- });
-
- registerOrgRoleMappingRoute({
- ...mockDependencies,
- router: mockRouter.router,
- });
- });
-
- it('creates a request handler', () => {
- expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
- path: '/ws/org/role_mappings/:id',
- });
- });
- });
-
describe('PUT /api/workplace_search/org/role_mappings/{id}', () => {
let mockRouter: MockRouter;
@@ -128,27 +101,4 @@ describe('role mappings routes', () => {
});
});
});
-
- describe('GET /api/workplace_search/org/role_mappings/new', () => {
- let mockRouter: MockRouter;
-
- beforeEach(() => {
- jest.clearAllMocks();
- mockRouter = new MockRouter({
- method: 'get',
- path: '/api/workplace_search/org/role_mappings/new',
- });
-
- registerOrgNewRoleMappingRoute({
- ...mockDependencies,
- router: mockRouter.router,
- });
- });
-
- it('creates a request handler', () => {
- expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
- path: '/ws/org/role_mappings/new',
- });
- });
- });
});
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts
index 5a6359c1cd836..a0fcec63cbb27 100644
--- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/role_mappings.ts
@@ -48,20 +48,6 @@ export function registerOrgRoleMappingRoute({
router,
enterpriseSearchRequestHandler,
}: RouteDependencies) {
- router.get(
- {
- path: '/api/workplace_search/org/role_mappings/{id}',
- validate: {
- params: schema.object({
- id: schema.string(),
- }),
- },
- },
- enterpriseSearchRequestHandler.createRequest({
- path: '/ws/org/role_mappings/:id',
- })
- );
-
router.put(
{
path: '/api/workplace_search/org/role_mappings/{id}',
@@ -92,23 +78,7 @@ export function registerOrgRoleMappingRoute({
);
}
-export function registerOrgNewRoleMappingRoute({
- router,
- enterpriseSearchRequestHandler,
-}: RouteDependencies) {
- router.get(
- {
- path: '/api/workplace_search/org/role_mappings/new',
- validate: false,
- },
- enterpriseSearchRequestHandler.createRequest({
- path: '/ws/org/role_mappings/new',
- })
- );
-}
-
export const registerRoleMappingsRoutes = (dependencies: RouteDependencies) => {
registerOrgRoleMappingsRoute(dependencies);
registerOrgRoleMappingRoute(dependencies);
- registerOrgNewRoleMappingRoute(dependencies);
};
From 6173c04c20538a8dc4c97e6053f7e0baf3b0e311 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Tue, 15 Jun 2021 11:39:32 -0400
Subject: [PATCH 56/91] [Snapshot + Restore] Migrate to new page layout
structure (#101811)
---
.../components/authorization_provider.tsx | 4 +-
.../authorization/components/index.ts | 4 +-
.../authorization/components/page_error.tsx | 72 ++++++++
.../components/section_error.tsx | 7 +-
.../authorization/index.ts | 4 +-
.../authorization/types.ts | 6 +
.../public/authorization/index.ts | 1 +
src/plugins/es_ui_shared/public/index.ts | 1 +
.../public/application/app.tsx | 16 +-
.../public/application/components/index.ts | 2 +-
.../public/application/components/loading.tsx | 66 +++++++
.../policy_form/steps/step_logistics.tsx | 6 +-
.../components/section_loading.tsx | 48 -----
.../public/application/sections/home/home.tsx | 139 ++++++---------
.../policy_details/policy_details.tsx | 5 +-
.../sections/policy_add/policy_add.tsx | 92 +++++-----
.../sections/policy_edit/policy_edit.tsx | 164 +++++++++---------
.../repository_add/repository_add.tsx | 36 ++--
.../repository_edit/repository_edit.tsx | 120 +++++++------
.../restore_snapshot/restore_snapshot.tsx | 64 ++++---
.../snapshot_restore/public/shared_imports.ts | 1 +
21 files changed, 464 insertions(+), 394 deletions(-)
create mode 100644 src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/page_error.tsx
create mode 100644 x-pack/plugins/snapshot_restore/public/application/components/loading.tsx
delete mode 100644 x-pack/plugins/snapshot_restore/public/application/components/section_loading.tsx
diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/authorization_provider.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/authorization_provider.tsx
index 14086e6ee8137..1352081eaa30b 100644
--- a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/authorization_provider.tsx
+++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/authorization_provider.tsx
@@ -11,9 +11,7 @@ import React, { createContext, useContext } from 'react';
import { useRequest } from '../../../public';
-import { Error as CustomError } from './section_error';
-
-import { Privileges } from '../types';
+import { Privileges, Error as CustomError } from '../types';
interface Authorization {
isLoading: boolean;
diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/index.ts
index b130602873724..f8eb7e3c7c0c8 100644
--- a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/index.ts
+++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/index.ts
@@ -16,4 +16,6 @@ export { WithPrivileges } from './with_privileges';
export { NotAuthorizedSection } from './not_authorized_section';
-export { Error, SectionError } from './section_error';
+export { SectionError } from './section_error';
+
+export { PageError } from './page_error';
diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/page_error.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/page_error.tsx
new file mode 100644
index 0000000000000..0a27b4098681b
--- /dev/null
+++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/page_error.tsx
@@ -0,0 +1,72 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { EuiSpacer, EuiEmptyPrompt, EuiPageContent } from '@elastic/eui';
+import React from 'react';
+import { APP_WRAPPER_CLASS } from '../../../../../../src/core/public';
+import { Error } from '../types';
+
+interface Props {
+ title: React.ReactNode;
+ error: Error;
+ actions?: JSX.Element;
+ isCentered?: boolean;
+}
+
+/*
+ * A reusable component to handle full page errors.
+ * This is based on Kibana design guidelines related
+ * to the new management navigation structure.
+ * In some scenarios, it replaces the usage of .
+ */
+
+export const PageError: React.FunctionComponent = ({
+ title,
+ error,
+ actions,
+ isCentered,
+ ...rest
+}) => {
+ const {
+ error: errorString,
+ cause, // wrapEsError() on the server adds a "cause" array
+ message,
+ } = error;
+
+ const errorContent = (
+
+ {title}}
+ body={
+ <>
+ {cause ? message || errorString : {message || errorString}
}
+ {cause && (
+ <>
+
+
+ {cause.map((causeMsg, i) => (
+ {causeMsg}
+ ))}
+
+ >
+ )}
+ >
+ }
+ iconType="alert"
+ actions={actions}
+ {...rest}
+ />
+
+ );
+
+ if (isCentered) {
+ return {errorContent}
;
+ }
+
+ return errorContent;
+};
diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/section_error.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/section_error.tsx
index c0b3533c8594b..a1652b4e153f5 100644
--- a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/section_error.tsx
+++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/section_error.tsx
@@ -8,12 +8,7 @@
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import React, { Fragment } from 'react';
-
-export interface Error {
- error: string;
- cause?: string[];
- message?: string;
-}
+import { Error } from '../types';
interface Props {
title: React.ReactNode;
diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/index.ts
index 089dc890c3e6c..e63d98512a2cd 100644
--- a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/index.ts
+++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/index.ts
@@ -12,8 +12,8 @@ export {
AuthorizationProvider,
AuthorizationContext,
SectionError,
- Error,
+ PageError,
useAuthorizationContext,
} from './components';
-export { Privileges, MissingPrivileges } from './types';
+export { Privileges, MissingPrivileges, Error } from './types';
diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/types.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/types.ts
index b10318aa415b3..70b54b0b6e425 100644
--- a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/types.ts
+++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/types.ts
@@ -14,3 +14,9 @@ export interface Privileges {
hasAllPrivileges: boolean;
missingPrivileges: MissingPrivileges;
}
+
+export interface Error {
+ error: string;
+ cause?: string[];
+ message?: string;
+}
diff --git a/src/plugins/es_ui_shared/public/authorization/index.ts b/src/plugins/es_ui_shared/public/authorization/index.ts
index 483fffd9c4859..f68ad3da2a4b5 100644
--- a/src/plugins/es_ui_shared/public/authorization/index.ts
+++ b/src/plugins/es_ui_shared/public/authorization/index.ts
@@ -14,6 +14,7 @@ export {
NotAuthorizedSection,
Privileges,
SectionError,
+ PageError,
useAuthorizationContext,
WithPrivileges,
} from '../../__packages_do_not_import__/authorization';
diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts
index b46a23994fe93..7b9013c043a0e 100644
--- a/src/plugins/es_ui_shared/public/index.ts
+++ b/src/plugins/es_ui_shared/public/index.ts
@@ -40,6 +40,7 @@ export {
Privileges,
MissingPrivileges,
SectionError,
+ PageError,
Error,
useAuthorizationContext,
} from './authorization';
diff --git a/x-pack/plugins/snapshot_restore/public/application/app.tsx b/x-pack/plugins/snapshot_restore/public/application/app.tsx
index 0c064d448105f..a7993300079ab 100644
--- a/x-pack/plugins/snapshot_restore/public/application/app.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/app.tsx
@@ -10,14 +10,16 @@ import { Redirect, Route, Switch } from 'react-router-dom';
import { EuiPageContent } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
+import { APP_WRAPPER_CLASS } from '../../../../../src/core/public';
+
import { APP_REQUIRED_CLUSTER_PRIVILEGES } from '../../common';
import {
useAuthorizationContext,
- SectionError,
+ PageError,
WithPrivileges,
NotAuthorizedSection,
} from '../shared_imports';
-import { SectionLoading } from './components';
+import { PageLoading } from './components';
import { DEFAULT_SECTION, Section } from './constants';
import {
RepositoryAdd,
@@ -42,7 +44,7 @@ export const App: React.FunctionComponent = () => {
const sectionsRegex = sections.join('|');
return apiError ? (
- {
`cluster.${name}`)}>
{({ isLoading, hasPrivileges, privilegesMissing }) =>
isLoading ? (
-
+
-
+
) : hasPrivileges ? (
-
+
@@ -84,7 +86,7 @@ export const App: React.FunctionComponent = () => {
) : (
-
+
= ({ children, ...rest }) => {
+ return (
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+};
+
+export const SectionLoading: React.FunctionComponent = ({ children }) => {
+ return (
+ }
+ body={{children} }
+ data-test-subj="sectionLoading"
+ />
+ );
+};
+
+/*
+ * Loading component used for full page loads.
+ * For tabbed sections, or within the context of a wizard,
+ * the component may be more appropriate
+ */
+export const PageLoading: React.FunctionComponent = ({ children }) => {
+ return (
+
+ }
+ body={{children} }
+ data-test-subj="sectionLoading"
+ />
+
+ );
+};
diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx
index 6443d774c9ac7..06c65a1713692 100644
--- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx
@@ -30,7 +30,7 @@ import { useCore, useServices } from '../../../app_context';
import { DEFAULT_POLICY_SCHEDULE, DEFAULT_POLICY_FREQUENCY } from '../../../constants';
import { useLoadRepositories } from '../../../services/http';
import { linkToAddRepository } from '../../../services/navigation';
-import { SectionLoading } from '../../';
+import { InlineLoading } from '../../';
import { StepProps } from './';
import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public';
@@ -174,12 +174,12 @@ export const PolicyStepLogistics: React.FunctionComponent = ({
const renderRepositorySelect = () => {
if (isLoadingRepositories) {
return (
-
+
-
+
);
}
diff --git a/x-pack/plugins/snapshot_restore/public/application/components/section_loading.tsx b/x-pack/plugins/snapshot_restore/public/application/components/section_loading.tsx
deleted file mode 100644
index c1548ad960bb0..0000000000000
--- a/x-pack/plugins/snapshot_restore/public/application/components/section_loading.tsx
+++ /dev/null
@@ -1,48 +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 React from 'react';
-
-import {
- EuiEmptyPrompt,
- EuiLoadingSpinner,
- EuiText,
- EuiFlexGroup,
- EuiFlexItem,
- EuiTextColor,
-} from '@elastic/eui';
-
-interface Props {
- inline?: boolean;
- children: React.ReactNode;
- [key: string]: any;
-}
-
-export const SectionLoading: React.FunctionComponent = ({ inline, children, ...rest }) => {
- if (inline) {
- return (
-
-
-
-
-
-
- {children}
-
-
-
- );
- }
-
- return (
- }
- body={{children} }
- data-test-subj="sectionLoading"
- />
- );
-};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx
index e4a23bac636d8..211d30181c25c 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx
@@ -9,18 +9,7 @@ import React, { useEffect } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
-import {
- EuiButtonEmpty,
- EuiFlexGroup,
- EuiFlexItem,
- EuiPageBody,
- EuiPageContent,
- EuiSpacer,
- EuiTab,
- EuiTabs,
- EuiTitle,
- EuiText,
-} from '@elastic/eui';
+import { EuiButtonEmpty, EuiPageHeader, EuiSpacer } from '@elastic/eui';
import { BASE_PATH, Section } from '../../constants';
import { useConfig, useCore } from '../../app_context';
@@ -100,79 +89,65 @@ export const SnapshotRestoreHome: React.FunctionComponent
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ <>
+
-
-
-
-
-
-
- {tabs.map((tab) => (
- onSectionChange(tab.id)}
- isSelected={tab.id === section}
- key={tab.id}
- data-test-subj={tab.id.toLowerCase() + '_tab'}
- >
- {tab.name}
-
- ))}
-
+
+ }
+ rightSideItems={[
+
+
+ ,
+ ]}
+ description={
+
+ }
+ tabs={tabs.map((tab) => ({
+ onClick: () => onSectionChange(tab.id),
+ isSelected: tab.id === section,
+ key: tab.id,
+ 'data-test-subj': tab.id.toLowerCase() + '_tab',
+ label: tab.name,
+ }))}
+ />
-
+
-
-
- {/* We have two separate SnapshotList routes because repository names could have slashes in
- * them. This would break a route with a path like snapshots/:repositoryName?/:snapshotId*
- */}
-
-
-
-
-
-
-
+
+
+ {/* We have two separate SnapshotList routes because repository names could have slashes in
+ * them. This would break a route with a path like snapshots/:repositoryName?/:snapshotId*
+ */}
+
+
+
+
+
+ >
);
};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx
index 2bad30b95081d..0a283d406e5aa 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx
@@ -40,6 +40,7 @@ import { linkToEditPolicy, linkToSnapshot } from '../../../../services/navigatio
import {
SectionLoading,
+ InlineLoading,
PolicyExecuteProvider,
PolicyDeleteProvider,
} from '../../../../components';
@@ -318,7 +319,7 @@ export const PolicyDetails: React.FunctionComponent = ({
{policyDetails && policyDetails.policy && policyDetails.policy.inProgress ? (
<>
-
+
= ({
values={{ snapshotName: policyDetails.policy.inProgress.snapshotName }}
/>
-
+
>
) : null}
{renderTabs()}
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx
index 7b1c10ec59e8a..3927b73abf093 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx
@@ -9,13 +9,13 @@ import React, { useEffect, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { RouteComponentProps } from 'react-router-dom';
-import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui';
+import { EuiPageContentBody, EuiSpacer, EuiPageHeader } from '@elastic/eui';
import { SlmPolicyPayload } from '../../../../common/types';
import { TIME_UNITS } from '../../../../common';
-import { SectionError, Error } from '../../../shared_imports';
+import { SectionError, PageError } from '../../../shared_imports';
-import { PolicyForm, SectionLoading } from '../../components';
+import { PolicyForm, PageLoading } from '../../components';
import { BASE_PATH, DEFAULT_POLICY_SCHEDULE } from '../../constants';
import { breadcrumbService, docTitleService } from '../../services/navigation';
import { addPolicy, useLoadIndices } from '../../services/http';
@@ -87,49 +87,57 @@ export const PolicyAdd: React.FunctionComponent = ({
setSaveError(null);
};
+ if (isLoadingIndices) {
+ return (
+
+
+
+ );
+ }
+
+ if (errorLoadingIndices) {
+ return (
+
+ }
+ error={errorLoadingIndices}
+ />
+ );
+ }
+
return (
-
-
-
-
+
+
-
-
-
- {isLoadingIndices ? (
-
-
-
- ) : errorLoadingIndices ? (
-
- }
- error={errorLoadingIndices as Error}
- />
- ) : (
-
- )}
-
-
+
+ }
+ />
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx
index 0ad1902845770..4ab0f15cc5523 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx
@@ -9,12 +9,12 @@ import React, { useEffect, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { RouteComponentProps } from 'react-router-dom';
-import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle, EuiCallOut } from '@elastic/eui';
+import { EuiPageContentBody, EuiPageHeader, EuiSpacer, EuiCallOut } from '@elastic/eui';
import { SlmPolicyPayload } from '../../../../common/types';
-import { SectionError, Error } from '../../../shared_imports';
+import { SectionError, Error, PageError } from '../../../shared_imports';
import { useDecodedParams } from '../../lib';
import { TIME_UNITS } from '../../../../common/constants';
-import { SectionLoading, PolicyForm } from '../../components';
+import { PageLoading, PolicyForm } from '../../components';
import { BASE_PATH } from '../../constants';
import { useServices } from '../../app_context';
import { breadcrumbService, docTitleService } from '../../services/navigation';
@@ -106,21 +106,39 @@ export const PolicyEdit: React.FunctionComponent {
+ return saveError ? (
+
+ }
+ error={saveError}
+ />
+ ) : null;
+ };
+
+ const clearSaveError = () => {
+ setSaveError(null);
+ };
+
const renderLoading = () => {
- return errorLoadingPolicy ? (
-
+ return isLoadingPolicy ? (
+
-
+
) : (
-
+
-
+
);
};
@@ -139,8 +157,9 @@ export const PolicyEdit: React.FunctionComponent
- }
- error={errorLoadingIndices as Error}
- />
- );
- }
- };
-
- const renderSaveError = () => {
- return saveError ? (
-
}
- error={saveError}
+ error={errorLoadingIndices as Error}
/>
- ) : null;
- };
-
- const clearSaveError = () => {
- setSaveError(null);
+ );
};
- const renderContent = () => {
- if (isLoadingPolicy || isLoadingIndices) {
- return renderLoading();
- }
- if (errorLoadingPolicy || errorLoadingIndices) {
- return renderError();
- }
+ if (isLoadingPolicy || isLoadingIndices) {
+ return renderLoading();
+ }
- return (
- <>
- {policy.isManagedPolicy ? (
- <>
-
- }
- />
-
- >
- ) : null}
-
- >
- );
- };
+ if (errorLoadingPolicy || errorLoadingIndices) {
+ return renderError();
+ }
return (
-
-
-
-
+
+
-
-
-
- {renderContent()}
-
-
+
+ }
+ />
+
+
+ {policy.isManagedPolicy ? (
+ <>
+
+ }
+ />
+
+ >
+ ) : null}
+
+
+
);
};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx
index 343c0b60a2253..100d345a49c4d 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx
@@ -10,7 +10,7 @@ import React, { useEffect, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { RouteComponentProps } from 'react-router-dom';
-import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui';
+import { EuiPageContentBody, EuiSpacer, EuiPageHeader } from '@elastic/eui';
import { Repository, EmptyRepository } from '../../../../common/types';
import { SectionError } from '../../../shared_imports';
@@ -79,25 +79,27 @@ export const RepositoryAdd: React.FunctionComponent = ({
};
return (
-
-
-
-
+
+
-
-
-
-
-
-
+
+ }
+ />
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx
index e27dd255f3bdf..9ecd1d0e3fafe 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx
@@ -5,15 +5,15 @@
* 2.0.
*/
-import React, { useEffect, useState, Fragment } from 'react';
+import React, { useEffect, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { RouteComponentProps } from 'react-router-dom';
-import { EuiCallOut, EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui';
+import { EuiCallOut, EuiPageContentBody, EuiPageHeader, EuiSpacer } from '@elastic/eui';
import { Repository, EmptyRepository } from '../../../../common/types';
-import { SectionError, Error } from '../../../shared_imports';
-import { RepositoryForm, SectionLoading } from '../../components';
+import { PageError, SectionError, Error } from '../../../shared_imports';
+import { RepositoryForm, PageLoading } from '../../components';
import { BASE_PATH, Section } from '../../constants';
import { useServices } from '../../app_context';
import { breadcrumbService, docTitleService } from '../../services/navigation';
@@ -79,12 +79,12 @@ export const RepositoryEdit: React.FunctionComponent {
return (
-
+
-
+
);
};
@@ -106,7 +106,7 @@ export const RepositoryEdit: React.FunctionComponent {
+ setSaveError(null);
+ };
+
const renderSaveError = () => {
return saveError ? (
{
- setSaveError(null);
- };
-
- const renderContent = () => {
- if (loadingRepository) {
- return renderLoading();
- }
- if (repositoryError) {
- return renderError();
- }
-
- const { isManagedRepository } = repositoryData;
-
- return (
-
- {isManagedRepository ? (
-
-
- }
- />
-
-
- ) : null}
-
-
- );
- };
-
return (
-
-
-
-
+
+
-
-
-
- {renderContent()}
-
-
+
+ }
+ />
+
+
+
+ {isManagedRepository ? (
+ <>
+
+ }
+ />
+
+ >
+ ) : null}
+
+
+
);
};
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx
index 685f3c9346f49..0f950ef3234ba 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx
@@ -8,12 +8,12 @@
import React, { useEffect, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { RouteComponentProps } from 'react-router-dom';
-import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui';
+import { EuiPageContentBody, EuiPageHeader, EuiSpacer } from '@elastic/eui';
import { SnapshotDetails, RestoreSettings } from '../../../../common/types';
-import { SectionError, Error } from '../../../shared_imports';
+import { SectionError, Error, PageError } from '../../../shared_imports';
import { BASE_PATH } from '../../constants';
-import { SectionLoading, RestoreSnapshotForm } from '../../components';
+import { PageLoading, RestoreSnapshotForm } from '../../components';
import { useServices } from '../../app_context';
import { breadcrumbService, docTitleService } from '../../services/navigation';
import { useLoadSnapshot, executeRestore } from '../../services/http';
@@ -76,12 +76,12 @@ export const RestoreSnapshot: React.FunctionComponent {
return (
-
+
-
+
);
};
@@ -103,8 +103,9 @@ export const RestoreSnapshot: React.FunctionComponent {
- if (loadingSnapshot) {
- return renderLoading();
- }
- if (snapshotError) {
- return renderError();
- }
+ if (loadingSnapshot) {
+ return renderLoading();
+ }
- return (
-
- );
- };
+ if (snapshotError) {
+ return renderError();
+ }
return (
-
-
-
-
+
+
-
-
-
- {renderContent()}
-
-
+
+ }
+ />
+
+
+
+
+
);
};
diff --git a/x-pack/plugins/snapshot_restore/public/shared_imports.ts b/x-pack/plugins/snapshot_restore/public/shared_imports.ts
index c38f0daedf996..759453edaba5d 100644
--- a/x-pack/plugins/snapshot_restore/public/shared_imports.ts
+++ b/x-pack/plugins/snapshot_restore/public/shared_imports.ts
@@ -12,6 +12,7 @@ export {
Frequency,
NotAuthorizedSection,
SectionError,
+ PageError,
sendRequest,
SendRequestConfig,
SendRequestResponse,
From 809279d13adadf77abe6848166f76f1dde4cb635 Mon Sep 17 00:00:00 2001
From: Dzmitry Lemechko
Date: Tue, 15 Jun 2021 17:53:53 +0200
Subject: [PATCH 57/91] [deps] update chromedriver to 91 (#102199)
---
package.json | 4 ++--
yarn.lock | 9 ++++-----
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/package.json b/package.json
index ff2f62f513084..c9c6fa7f582c5 100644
--- a/package.json
+++ b/package.json
@@ -670,7 +670,7 @@
"callsites": "^3.1.0",
"chai": "3.5.0",
"chance": "1.0.18",
- "chromedriver": "^90.0.0",
+ "chromedriver": "^91.0.1",
"clean-webpack-plugin": "^3.0.0",
"cmd-shim": "^2.1.0",
"compression-webpack-plugin": "^4.0.0",
@@ -838,4 +838,4 @@
"yargs": "^15.4.1",
"zlib": "^1.0.5"
}
-}
+}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index a12920f72ba82..a9a81585000b5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9293,17 +9293,16 @@ chrome-trace-event@^1.0.2:
dependencies:
tslib "^1.9.0"
-chromedriver@^90.0.0:
- version "90.0.0"
- resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-90.0.0.tgz#1b18960a31a12884981bdc270b43c4356ce7a65a"
- integrity sha512-k+GMmNb7cmuCCctQvUIeNxDGSq8DJauO+UKQS2qLT8aA36CPEcv8rpFepf6lRkNaIlfwdCUt/0B5bZDw3wY2yw==
+chromedriver@^91.0.1:
+ version "91.0.1"
+ resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-91.0.1.tgz#4d70a569901e356c978a41de3019c464f2a8ebd0"
+ integrity sha512-9LktpHiUxM4UWUsr+jI1K1YKx2GENt6BKKJ2mibPj1Wc6ODzX/3fFIlr8CZ4Ftuyga+dHTTbAyPWKwKvybEbKA==
dependencies:
"@testim/chrome-version" "^1.0.7"
axios "^0.21.1"
del "^6.0.0"
extract-zip "^2.0.1"
https-proxy-agent "^5.0.0"
- mkdirp "^1.0.4"
proxy-from-env "^1.1.0"
tcp-port-used "^1.0.1"
From 035f617b58bb00d978c272ad8df28bfd99d7ac27 Mon Sep 17 00:00:00 2001
From: Tyler Smalley
Date: Tue, 15 Jun 2021 09:00:30 -0700
Subject: [PATCH 58/91] skip flaky suite (#102012)
---
x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts
index 63326448ec1e5..777e6fd598f45 100644
--- a/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts
@@ -67,7 +67,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
let testJobId = '';
- describe('anomaly detection alert', function () {
+ // Failing: See https://github.com/elastic/kibana/issues/102012
+ describe.skip('anomaly detection alert', function () {
this.tags('ciGroup13');
before(async () => {
From ff5ecc342081f415bed7c189b9b6bd8cfd906c09 Mon Sep 17 00:00:00 2001
From: Marius Dragomir
Date: Tue, 15 Jun 2021 18:17:19 +0200
Subject: [PATCH 59/91] [QA]Skip of metricbeat dashboard test (#97174)
* fix flakyness of metricbeat dashboard test
* skip test for now
* Update _metricbeat_dashboard.js
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../apps/metricbeat/_metricbeat_dashboard.js | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js b/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js
index b678e88bcf0df..502c950d2b113 100644
--- a/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js
+++ b/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.js
@@ -15,6 +15,8 @@ const ARCHIVE = resolve(INTEGRATION_TEST_ROOT, 'test/es_archives/metricbeat');
export default function ({ getService, getPageObjects, updateBaselines }) {
const screenshot = getService('screenshots');
const browser = getService('browser');
+ const find = getService('find');
+ const log = getService('log');
const esArchiver = getService('esArchiver');
const PageObjects = getPageObjects(['common', 'dashboard', 'timePicker']);
@@ -28,7 +30,7 @@ export default function ({ getService, getPageObjects, updateBaselines }) {
'dashboard',
'view/Metricbeat-system-overview-ecs?_g=(filters:!(),refreshInterval:(pause:!t,value:0),' +
'time:(from:%272020-09-29T19:02:37.902Z%27,to:%272020-09-29T19:06:43.218Z%27))&_a=' +
- '(description:%27Overview%20of%20system%20metrics%27,filters:!(),fullScreenMode:!t,' +
+ '(description:%27Overview%20of%20system%20metrics%27,filters:!(),' +
'options:(darkTheme:!f),query:(language:kuery,query:%27%27),timeRestore:!f,' +
'title:%27%5BMetricbeat%20System%5D%20Overview%20ECS%27,viewMode:view)',
{
@@ -45,6 +47,7 @@ export default function ({ getService, getPageObjects, updateBaselines }) {
// await PageObjects.dashboard.clickFullScreenMode();
await PageObjects.common.sleep(2000);
+ await find.clickByButtonText('Dismiss');
await PageObjects.dashboard.waitForRenderComplete();
await browser.setScreenshotSize(1000, 1000);
});
@@ -61,7 +64,7 @@ export default function ({ getService, getPageObjects, updateBaselines }) {
);
expect(percentDifference).to.be.lessThan(0.01);
} finally {
- await PageObjects.dashboard.clickExitFullScreenLogoButton();
+ log.debug('### Screenshot taken');
}
});
});
From 2c17897f062cdbb9ddbd1553f3c2c1b73eeba808 Mon Sep 17 00:00:00 2001
From: Wylie Conlon
Date: Tue, 15 Jun 2021 12:28:13 -0400
Subject: [PATCH 60/91] Update aggregation reference docs for 7.14 (#101931)
* Update aggregation reference docs for 7.13
* Add more reference about filtered metrics
* [Lens] Document updates for 7.14
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
docs/user/dashboard/aggregation-reference.asciidoc | 13 ++++++++++---
.../dashboard/create-panels-with-editors.asciidoc | 5 ++++-
docs/user/dashboard/lens.asciidoc | 4 +++-
3 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/docs/user/dashboard/aggregation-reference.asciidoc b/docs/user/dashboard/aggregation-reference.asciidoc
index 39e596df4af34..001114578a1cd 100644
--- a/docs/user/dashboard/aggregation-reference.asciidoc
+++ b/docs/user/dashboard/aggregation-reference.asciidoc
@@ -23,7 +23,7 @@ This reference can help simplify the comparison if you need a specific feature.
| Table with summary row
^| X
-|
+^| X
|
|
|
@@ -65,7 +65,7 @@ This reference can help simplify the comparison if you need a specific feature.
| Heat map
^| X
-|
+^| X
|
|
^| X
@@ -333,7 +333,7 @@ build their advanced visualization.
| Math on aggregated data
|
-|
+^| X
^| X
^| X
^| X
@@ -352,6 +352,13 @@ build their advanced visualization.
^| X
^| X
+| Time shifts
+|
+^| X
+^| X
+^| X
+^| X
+
| Fully custom {es} queries
|
|
diff --git a/docs/user/dashboard/create-panels-with-editors.asciidoc b/docs/user/dashboard/create-panels-with-editors.asciidoc
index 17d3b5fb8a8a5..77a4706e249fd 100644
--- a/docs/user/dashboard/create-panels-with-editors.asciidoc
+++ b/docs/user/dashboard/create-panels-with-editors.asciidoc
@@ -30,13 +30,16 @@
[[lens-editor]]
=== Lens
-*Lens* is the drag and drop editor that creates visualizations of your data.
+*Lens* is the drag and drop editor that creates visualizations of your data, recommended for most
+users.
With *Lens*, you can:
* Use the automatically generated suggestions to change the visualization type.
* Create visualizations with multiple layers and indices.
* Change the aggregation and labels to customize the data.
+* Perform math on aggregations using *Formula*.
+* Use time shifts to compare data at two times, such as month over month.
[role="screenshot"]
image:dashboard/images/lens_advanced_1_1.png[Lens]
diff --git a/docs/user/dashboard/lens.asciidoc b/docs/user/dashboard/lens.asciidoc
index 9f17a380bc209..7927489c596d7 100644
--- a/docs/user/dashboard/lens.asciidoc
+++ b/docs/user/dashboard/lens.asciidoc
@@ -300,7 +300,9 @@ image::images/lens_missing_values_strategy.png[Lens Missing values strategies me
[[is-it-possible-to-change-the-scale-of-Y-axis]]
===== Is it possible to statically define the scale of the y-axis in a visualization?
-The ability to start the y-axis from another value than 0, or use a logarithmic scale, is unsupported in *Lens*.
+Yes, you can set the bounds on bar, line and area chart types in Lens, unless using percentage mode. Bar
+and area charts must have 0 in the bounds. Logarithmic scales are unsupported in *Lens*.
+To set the y-axis bounds, click the icon representing the axis you want to customize.
[float]
[[is-it-possible-to-have-pagination-for-datatable]]
From 7683d9ef17148d381420363eb4c56d10b552a2db Mon Sep 17 00:00:00 2001
From: Vadim Yakhin
Date: Tue, 15 Jun 2021 13:50:43 -0300
Subject: [PATCH 61/91] [Workplace Search] Fix sidebar copy for Account
Settings page (#102110)
* Rename PrivateSourcesLayout to PersonalDashboardLayout
The renaming was required because this layout is also used for Account Settings.
* Rename imports
* Rename files
* More personal_dashboard_layout from sources folder to layout folder
* Extract PrivateSourcesSidebar from PersonalDashboardLayout
This is needed to add the ability to pass AccountSettingsSidebar in the
next commit.
* Add and use AccountSettingsSidebar
* Move related styles to personal_dashboard_layout folder
* Move tests from personal_dashboard_layout to private_sources_sidebar
* Add tests for account_settings_sidebar
* Remove redundant import
* Move source-related sidebar text constants to top-level constants file
Now all personal dashboard sidebar copy is in one place
---
.../components/layout/index.ts | 2 +
.../layout/personal_dashboard_layout/index.ts | 8 ++
.../personal_dashboard_layout.scss | 23 ++++++
.../personal_dashboard_layout.test.tsx | 41 +++++++++++
.../personal_dashboard_layout.tsx | 51 +++++++++++++
.../account_settings_sidebar.test.tsx | 22 ++++++
.../account_settings_sidebar.tsx | 17 +++++
.../personal_dashboard_sidebar/index.ts | 9 +++
.../private_sources_sidebar.test.tsx} | 38 +++-------
.../private_sources_sidebar.tsx | 40 ++++++++++
.../workplace_search/constants.ts | 43 +++++++++++
.../applications/workplace_search/index.tsx | 22 ++++--
.../views/content_sources/constants.ts | 29 --------
.../views/content_sources/index.ts | 1 -
.../private_sources_layout.tsx | 73 -------------------
.../views/content_sources/sources.scss | 17 -----
16 files changed, 285 insertions(+), 151 deletions(-)
create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/index.ts
create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/personal_dashboard_layout.scss
create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/personal_dashboard_layout.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/personal_dashboard_layout.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/account_settings_sidebar.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/account_settings_sidebar.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/index.ts
rename x-pack/plugins/enterprise_search/public/applications/workplace_search/{views/content_sources/private_sources_layout.test.tsx => components/layout/personal_dashboard_sidebar/private_sources_sidebar.test.tsx} (54%)
create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.tsx
delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources_layout.tsx
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts
index b9a49c416f283..e1c2a3b76e3ff 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts
@@ -8,3 +8,5 @@
export { WorkplaceSearchNav } from './nav';
export { WorkplaceSearchHeaderActions } from './kibana_header_actions';
export { AccountHeader } from './account_header';
+export { PersonalDashboardLayout } from './personal_dashboard_layout';
+export { PrivateSourcesSidebar, AccountSettingsSidebar } from './personal_dashboard_sidebar';
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/index.ts
new file mode 100644
index 0000000000000..40347aaee747d
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/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 { PersonalDashboardLayout } from './personal_dashboard_layout';
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/personal_dashboard_layout.scss b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/personal_dashboard_layout.scss
new file mode 100644
index 0000000000000..175f6b9ebca20
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/personal_dashboard_layout.scss
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+.personalDashboardLayout {
+ $sideBarWidth: $euiSize * 30;
+ $consoleHeaderHeight: 48px; // NOTE: Keep an eye on this for changes
+ $pageHeight: calc(100vh - #{$consoleHeaderHeight});
+
+ left: $sideBarWidth;
+ width: calc(100% - #{$sideBarWidth});
+ min-height: $pageHeight;
+
+ &__sideBar {
+ padding: 32px 40px 40px;
+ width: $sideBarWidth;
+ margin-left: -$sideBarWidth;
+ height: $pageHeight;
+ }
+}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/personal_dashboard_layout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/personal_dashboard_layout.test.tsx
new file mode 100644
index 0000000000000..faeaa7323e93f
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/personal_dashboard_layout.test.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { EuiCallOut } from '@elastic/eui';
+
+import { AccountHeader } from '..';
+
+import { PersonalDashboardLayout } from './personal_dashboard_layout';
+
+describe('PersonalDashboardLayout', () => {
+ const children = test
;
+ const sidebar = test
;
+
+ it('renders', () => {
+ const wrapper = shallow(
+ {children}
+ );
+
+ expect(wrapper.find('[data-test-subj="TestChildren"]')).toHaveLength(1);
+ expect(wrapper.find('[data-test-subj="TestSidebar"]')).toHaveLength(1);
+ expect(wrapper.find(AccountHeader)).toHaveLength(1);
+ });
+
+ it('renders callout when in read-only mode', () => {
+ const wrapper = shallow(
+
+ {children}
+
+ );
+
+ expect(wrapper.find(EuiCallOut)).toHaveLength(1);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/personal_dashboard_layout.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/personal_dashboard_layout.tsx
new file mode 100644
index 0000000000000..1ab9e07dfa14d
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_layout/personal_dashboard_layout.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { EuiPage, EuiPageSideBar, EuiPageBody, EuiCallOut } from '@elastic/eui';
+
+import { AccountHeader } from '..';
+
+import { PRIVATE_DASHBOARD_READ_ONLY_MODE_WARNING } from '../../../views/content_sources/constants';
+
+import './personal_dashboard_layout.scss';
+
+interface LayoutProps {
+ restrictWidth?: boolean;
+ readOnlyMode?: boolean;
+ sidebar: React.ReactNode;
+}
+
+export const PersonalDashboardLayout: React.FC = ({
+ children,
+ restrictWidth,
+ readOnlyMode,
+ sidebar,
+}) => {
+ return (
+ <>
+
+
+
+ {sidebar}
+
+
+ {readOnlyMode && (
+
+ )}
+ {children}
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/account_settings_sidebar.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/account_settings_sidebar.test.tsx
new file mode 100644
index 0000000000000..8edcf83a57e6f
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/account_settings_sidebar.test.tsx
@@ -0,0 +1,22 @@
+/*
+ * 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 { shallow } from 'enzyme';
+
+import { ViewContentHeader } from '../../shared/view_content_header';
+
+import { AccountSettingsSidebar } from './account_settings_sidebar';
+
+describe('AccountSettingsSidebar', () => {
+ it('renders', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(ViewContentHeader)).toHaveLength(1);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/account_settings_sidebar.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/account_settings_sidebar.tsx
new file mode 100644
index 0000000000000..490f3ff0ae4a5
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/account_settings_sidebar.tsx
@@ -0,0 +1,17 @@
+/*
+ * 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 { ACCOUNT_SETTINGS_TITLE, ACCOUNT_SETTINGS_DESCRIPTION } from '../../../constants';
+import { ViewContentHeader } from '../../shared/view_content_header';
+
+export const AccountSettingsSidebar = () => {
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/index.ts
new file mode 100644
index 0000000000000..ffd241000c8a0
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/index.ts
@@ -0,0 +1,9 @@
+/*
+ * 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 { PrivateSourcesSidebar } from './private_sources_sidebar';
+export { AccountSettingsSidebar } from './account_settings_sidebar';
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources_layout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.test.tsx
similarity index 54%
rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources_layout.test.tsx
rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.test.tsx
index 6af439814b891..387724af970f8 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources_layout.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.test.tsx
@@ -5,50 +5,42 @@
* 2.0.
*/
-import '../../../__mocks__/shallow_useeffect.mock';
-
-import { setMockValues } from '../../../__mocks__/kea_logic';
+import { setMockValues } from '../../../../__mocks__/kea_logic';
import React from 'react';
import { shallow } from 'enzyme';
-import { EuiCallOut } from '@elastic/eui';
-
-import { AccountHeader } from '../../components/layout';
-import { ViewContentHeader } from '../../components/shared/view_content_header';
-
-import { SourceSubNav } from './components/source_sub_nav';
-
import {
PRIVATE_CAN_CREATE_PAGE_TITLE,
PRIVATE_VIEW_ONLY_PAGE_TITLE,
PRIVATE_VIEW_ONLY_PAGE_DESCRIPTION,
PRIVATE_CAN_CREATE_PAGE_DESCRIPTION,
-} from './constants';
-import { PrivateSourcesLayout } from './private_sources_layout';
+} from '../../../constants';
+import { SourceSubNav } from '../../../views/content_sources/components/source_sub_nav';
+
+import { ViewContentHeader } from '../../shared/view_content_header';
-describe('PrivateSourcesLayout', () => {
+import { PrivateSourcesSidebar } from './private_sources_sidebar';
+
+describe('PrivateSourcesSidebar', () => {
const mockValues = {
account: { canCreatePersonalSources: true },
};
- const children = test
;
-
beforeEach(() => {
setMockValues({ ...mockValues });
});
it('renders', () => {
- const wrapper = shallow({children} );
+ const wrapper = shallow( );
- expect(wrapper.find('[data-test-subj="TestChildren"]')).toHaveLength(1);
+ expect(wrapper.find(ViewContentHeader)).toHaveLength(1);
expect(wrapper.find(SourceSubNav)).toHaveLength(1);
- expect(wrapper.find(AccountHeader)).toHaveLength(1);
});
it('uses correct title and description when private sources are enabled', () => {
- const wrapper = shallow({children} );
+ const wrapper = shallow( );
expect(wrapper.find(ViewContentHeader).prop('title')).toEqual(PRIVATE_CAN_CREATE_PAGE_TITLE);
expect(wrapper.find(ViewContentHeader).prop('description')).toEqual(
@@ -58,17 +50,11 @@ describe('PrivateSourcesLayout', () => {
it('uses correct title and description when private sources are disabled', () => {
setMockValues({ account: { canCreatePersonalSources: false } });
- const wrapper = shallow({children} );
+ const wrapper = shallow( );
expect(wrapper.find(ViewContentHeader).prop('title')).toEqual(PRIVATE_VIEW_ONLY_PAGE_TITLE);
expect(wrapper.find(ViewContentHeader).prop('description')).toEqual(
PRIVATE_VIEW_ONLY_PAGE_DESCRIPTION
);
});
-
- it('renders callout when in read-only mode', () => {
- const wrapper = shallow({children} );
-
- expect(wrapper.find(EuiCallOut)).toHaveLength(1);
- });
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.tsx
new file mode 100644
index 0000000000000..5505ae57b2ad5
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.tsx
@@ -0,0 +1,40 @@
+/*
+ * 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 { useValues } from 'kea';
+
+import { AppLogic } from '../../../app_logic';
+import {
+ PRIVATE_CAN_CREATE_PAGE_TITLE,
+ PRIVATE_VIEW_ONLY_PAGE_TITLE,
+ PRIVATE_VIEW_ONLY_PAGE_DESCRIPTION,
+ PRIVATE_CAN_CREATE_PAGE_DESCRIPTION,
+} from '../../../constants';
+import { SourceSubNav } from '../../../views/content_sources/components/source_sub_nav';
+import { ViewContentHeader } from '../../shared/view_content_header';
+
+export const PrivateSourcesSidebar = () => {
+ const {
+ account: { canCreatePersonalSources },
+ } = useValues(AppLogic);
+
+ const PAGE_TITLE = canCreatePersonalSources
+ ? PRIVATE_CAN_CREATE_PAGE_TITLE
+ : PRIVATE_VIEW_ONLY_PAGE_TITLE;
+ const PAGE_DESCRIPTION = canCreatePersonalSources
+ ? PRIVATE_CAN_CREATE_PAGE_DESCRIPTION
+ : PRIVATE_VIEW_ONLY_PAGE_DESCRIPTION;
+
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts
index dcebc35d45f71..aa5419f12c7f3 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts
@@ -662,6 +662,49 @@ export const PRIVATE_SOURCES = i18n.translate(
}
);
+export const PRIVATE_CAN_CREATE_PAGE_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.sources.private.canCreate.title',
+ {
+ defaultMessage: 'Manage private content sources',
+ }
+);
+
+export const PRIVATE_VIEW_ONLY_PAGE_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.title',
+ {
+ defaultMessage: 'Review Group Sources',
+ }
+);
+
+export const PRIVATE_VIEW_ONLY_PAGE_DESCRIPTION = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.description',
+ {
+ defaultMessage: 'Review the status of all sources shared with your Group.',
+ }
+);
+
+export const PRIVATE_CAN_CREATE_PAGE_DESCRIPTION = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.sources.private.canCreate.description',
+ {
+ defaultMessage:
+ 'Review the status of all connected private sources, and manage private sources for your account.',
+ }
+);
+
+export const ACCOUNT_SETTINGS_TITLE = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.accountSettings.title',
+ {
+ defaultMessage: 'Account Settings',
+ }
+);
+
+export const ACCOUNT_SETTINGS_DESCRIPTION = i18n.translate(
+ 'xpack.enterpriseSearch.workplaceSearch.accountSettings.description',
+ {
+ defaultMessage: 'Manage access, passwords, and other account settings.',
+ }
+);
+
export const CONFIRM_CHANGES_TEXT = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.confirmChanges.text',
{
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
index 0fc8a6e7c7c0d..7e911b31c516b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
@@ -19,6 +19,11 @@ import { NotFound } from '../shared/not_found';
import { AppLogic } from './app_logic';
import { WorkplaceSearchNav, WorkplaceSearchHeaderActions } from './components/layout';
+import {
+ PersonalDashboardLayout,
+ PrivateSourcesSidebar,
+ AccountSettingsSidebar,
+} from './components/layout';
import {
GROUPS_PATH,
SETUP_GUIDE_PATH,
@@ -34,7 +39,6 @@ import { AccountSettings } from './views/account_settings';
import { SourcesRouter } from './views/content_sources';
import { SourceAdded } from './views/content_sources/components/source_added';
import { SourceSubNav } from './views/content_sources/components/source_sub_nav';
-import { PrivateSourcesLayout } from './views/content_sources/private_sources_layout';
import { ErrorState } from './views/error_state';
import { GroupsRouter } from './views/groups';
import { GroupSubNav } from './views/groups/components/group_sub_nav';
@@ -101,14 +105,22 @@ export const WorkplaceSearchConfigured: React.FC = (props) => {
)}
-
+ }
+ >
-
+
-
+ }
+ >
-
+
= ({
- children,
- restrictWidth,
- readOnlyMode,
-}) => {
- const {
- account: { canCreatePersonalSources },
- } = useValues(AppLogic);
-
- const PAGE_TITLE = canCreatePersonalSources
- ? PRIVATE_CAN_CREATE_PAGE_TITLE
- : PRIVATE_VIEW_ONLY_PAGE_TITLE;
- const PAGE_DESCRIPTION = canCreatePersonalSources
- ? PRIVATE_CAN_CREATE_PAGE_DESCRIPTION
- : PRIVATE_VIEW_ONLY_PAGE_DESCRIPTION;
-
- return (
- <>
-
-
-
-
-
-
-
- {readOnlyMode && (
-
- )}
- {children}
-
-
- >
- );
-};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources.scss b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources.scss
index 549ca3ae9154e..feccc0e1924d2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources.scss
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources.scss
@@ -18,23 +18,6 @@
}
}
-.privateSourcesLayout {
- $sideBarWidth: $euiSize * 30;
- $consoleHeaderHeight: 48px; // NOTE: Keep an eye on this for changes
- $pageHeight: calc(100vh - #{$consoleHeaderHeight});
-
- left: $sideBarWidth;
- width: calc(100% - #{$sideBarWidth});
- min-height: $pageHeight;
-
- &__sideBar {
- padding: 32px 40px 40px;
- width: $sideBarWidth;
- margin-left: -$sideBarWidth;
- height: $pageHeight;
- }
-}
-
.sourcesSubNav {
li {
display: block;
From 35cc59b571d19fe52eff17777a4613fd867ff928 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?=
Date: Tue, 15 Jun 2021 20:52:20 +0300
Subject: [PATCH 62/91] [Osquery] Add support for platform and version fields
(#101835)
---
.../forms/hook_form_lib/hooks/use_form.ts | 2 +-
.../server/services/package_policy.test.ts | 123 ++++++++++++
.../fleet/server/services/package_policy.ts | 11 +-
x-pack/plugins/osquery/common/types.ts | 30 +++
.../action_results/action_results_summary.tsx | 3 +-
.../agent_policies/agents_policy_link.tsx | 5 +-
.../components/manage_integration_link.tsx | 9 +-
.../fleet_integration/navigation_buttons.tsx | 15 +-
...managed_policy_create_import_extension.tsx | 21 ++-
.../scheduled_query_groups/details/index.tsx | 2 +-
.../form/add_query_flyout.tsx | 128 -------------
.../form/edit_query_flyout.tsx | 140 --------------
.../scheduled_query_groups/form/index.tsx | 94 +++++++---
.../form/queries_field.tsx | 166 +++++++++++------
.../queries/constants.ts | 72 +++++++
.../queries/platform_checkbox_group_field.tsx | 134 +++++++++++++
.../queries/platforms/constants.ts | 10 +
.../queries/platforms/helpers.tsx | 55 ++++++
.../queries/platforms/index.tsx | 50 +++++
.../queries/platforms/logos/linux.svg | 1 +
.../queries/platforms/logos/macos.svg | 1 +
.../queries/platforms/logos/windows.svg | 1 +
.../queries/platforms/platform_icon.tsx | 21 +++
.../queries/platforms/types.ts | 12 ++
.../queries/query_flyout.tsx | 176 ++++++++++++++++++
.../scheduled_query_groups/queries/schema.tsx | 70 +++++++
.../use_scheduled_query_group_query_form.tsx | 74 ++++++++
.../{form => queries}/validations.ts | 0
.../scheduled_query_group_queries_table.tsx | 59 ++++--
.../use_scheduled_query_group.ts | 13 +-
.../plugins/osquery/public/shared_imports.ts | 1 +
x-pack/plugins/osquery/tsconfig.json | 3 +-
32 files changed, 1115 insertions(+), 387 deletions(-)
delete mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/form/add_query_flyout.tsx
delete mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/form/edit_query_flyout.tsx
create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/queries/constants.ts
create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/queries/platform_checkbox_group_field.tsx
create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/constants.ts
create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/helpers.tsx
create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/index.tsx
create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/logos/linux.svg
create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/logos/macos.svg
create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/logos/windows.svg
create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/platform_icon.tsx
create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/types.ts
create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx
create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx
create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/queries/use_scheduled_query_group_query_form.tsx
rename x-pack/plugins/osquery/public/scheduled_query_groups/{form => queries}/validations.ts (100%)
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts
index 181bd9959c1bb..fb334afb22b13 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts
@@ -18,7 +18,7 @@ const DEFAULT_OPTIONS = {
stripEmptyFields: true,
};
-interface UseFormReturn {
+export interface UseFormReturn {
form: FormHook;
}
diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts
index a6958ba88449a..b3626a83c41d1 100644
--- a/x-pack/plugins/fleet/server/services/package_policy.test.ts
+++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts
@@ -688,6 +688,129 @@ describe('Package policy service', () => {
expect(modifiedStream.vars!.paths.value).toEqual(expect.arrayContaining(['north', 'south']));
expect(modifiedStream.vars!.period.value).toEqual('12mo');
});
+
+ it('should add new input vars when updating', async () => {
+ const savedObjectsClient = savedObjectsClientMock.create();
+ const mockPackagePolicy = createPackagePolicyMock();
+ const mockInputs = [
+ {
+ config: {},
+ enabled: true,
+ keep_enabled: true,
+ type: 'endpoint',
+ vars: {
+ dog: {
+ type: 'text',
+ value: 'dalmatian',
+ },
+ cat: {
+ type: 'text',
+ value: 'siamese',
+ frozen: true,
+ },
+ },
+ streams: [
+ {
+ data_stream: {
+ type: 'birds',
+ dataset: 'migratory.patterns',
+ },
+ enabled: false,
+ id: `endpoint-migratory.patterns-${mockPackagePolicy.id}`,
+ vars: {
+ paths: {
+ value: ['north', 'south'],
+ type: 'text',
+ frozen: true,
+ },
+ },
+ },
+ ],
+ },
+ ];
+ const inputsUpdate = [
+ {
+ config: {},
+ enabled: false,
+ type: 'endpoint',
+ vars: {
+ dog: {
+ type: 'text',
+ value: 'labrador',
+ },
+ cat: {
+ type: 'text',
+ value: 'tabby',
+ },
+ },
+ streams: [
+ {
+ data_stream: {
+ type: 'birds',
+ dataset: 'migratory.patterns',
+ },
+ enabled: false,
+ id: `endpoint-migratory.patterns-${mockPackagePolicy.id}`,
+ vars: {
+ paths: {
+ value: ['east', 'west'],
+ type: 'text',
+ },
+ period: {
+ value: '12mo',
+ type: 'text',
+ },
+ },
+ },
+ ],
+ },
+ ];
+ const attributes = {
+ ...mockPackagePolicy,
+ inputs: mockInputs,
+ };
+
+ savedObjectsClient.get.mockResolvedValue({
+ id: 'test',
+ type: 'abcd',
+ references: [],
+ version: 'test',
+ attributes,
+ });
+
+ savedObjectsClient.update.mockImplementation(
+ async (
+ type: string,
+ id: string,
+ attrs: any
+ ): Promise> => {
+ savedObjectsClient.get.mockResolvedValue({
+ id: 'test',
+ type: 'abcd',
+ references: [],
+ version: 'test',
+ attributes: attrs,
+ });
+ return attrs;
+ }
+ );
+ const elasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
+
+ const result = await packagePolicyService.update(
+ savedObjectsClient,
+ elasticsearchClient,
+ 'the-package-policy-id',
+ { ...mockPackagePolicy, inputs: inputsUpdate }
+ );
+
+ const [modifiedInput] = result.inputs;
+ expect(modifiedInput.enabled).toEqual(true);
+ expect(modifiedInput.vars!.dog.value).toEqual('labrador');
+ expect(modifiedInput.vars!.cat.value).toEqual('siamese');
+ const [modifiedStream] = modifiedInput.streams;
+ expect(modifiedStream.vars!.paths.value).toEqual(expect.arrayContaining(['north', 'south']));
+ expect(modifiedStream.vars!.period.value).toEqual('12mo');
+ });
});
describe('runExternalCallbacks', () => {
diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts
index 93bcef458279c..1cda159429984 100644
--- a/x-pack/plugins/fleet/server/services/package_policy.ts
+++ b/x-pack/plugins/fleet/server/services/package_policy.ts
@@ -649,11 +649,16 @@ function _enforceFrozenVars(
newVars: Record
) {
const resultVars: Record = {};
+ for (const [key, val] of Object.entries(newVars)) {
+ if (oldVars[key]?.frozen) {
+ resultVars[key] = oldVars[key];
+ } else {
+ resultVars[key] = val;
+ }
+ }
for (const [key, val] of Object.entries(oldVars)) {
- if (val.frozen) {
+ if (!newVars[key] && val.frozen) {
resultVars[key] = val;
- } else {
- resultVars[key] = newVars[key];
}
}
return resultVars;
diff --git a/x-pack/plugins/osquery/common/types.ts b/x-pack/plugins/osquery/common/types.ts
index 11c418a51fc7c..ab9e36ec335db 100644
--- a/x-pack/plugins/osquery/common/types.ts
+++ b/x-pack/plugins/osquery/common/types.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { PackagePolicy, PackagePolicyInput, PackagePolicyInputStream } from '../../fleet/common';
+
export const savedQuerySavedObjectType = 'osquery-saved-query';
export const packSavedObjectType = 'osquery-pack';
export type SavedObjectType = 'osquery-saved-query' | 'osquery-pack';
@@ -25,3 +27,31 @@ export type RequiredKeepUndefined = { [K in keyof T]-?: [T[K]] } extends infe
? { [K in keyof U]: U[K][0] }
: never
: never;
+
+export interface OsqueryManagerPackagePolicyConfigRecordEntry {
+ type: string;
+ value: string;
+ frozen?: boolean;
+}
+
+export interface OsqueryManagerPackagePolicyConfigRecord {
+ id: OsqueryManagerPackagePolicyConfigRecordEntry;
+ query: OsqueryManagerPackagePolicyConfigRecordEntry;
+ interval: OsqueryManagerPackagePolicyConfigRecordEntry;
+ platform?: OsqueryManagerPackagePolicyConfigRecordEntry;
+ version?: OsqueryManagerPackagePolicyConfigRecordEntry;
+}
+
+export interface OsqueryManagerPackagePolicyInputStream
+ extends Omit {
+ config?: OsqueryManagerPackagePolicyConfigRecord;
+ vars?: OsqueryManagerPackagePolicyConfigRecord;
+}
+
+export interface OsqueryManagerPackagePolicyInput extends Omit {
+ streams: OsqueryManagerPackagePolicyInputStream[];
+}
+
+export interface OsqueryManagerPackagePolicy extends Omit {
+ inputs: OsqueryManagerPackagePolicyInput[];
+}
diff --git a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
index ffa86c547656c..23277976968a9 100644
--- a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
+++ b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx
@@ -23,6 +23,7 @@ import {
import React, { useCallback, useMemo, useState } from 'react';
import styled from 'styled-components';
+import { PLUGIN_ID } from '../../../fleet/common';
import { pagePathGetters } from '../../../fleet/public';
import { useActionResults } from './use_action_results';
import { useAllResults } from '../results/use_all_results';
@@ -130,7 +131,7 @@ const ActionResultsSummaryComponent: React.FC = ({
(agentId) => (
= ({ policyId }
const href = useMemo(
() =>
- getUrlForApp('fleet', {
+ getUrlForApp(PLUGIN_ID, {
path: `#` + pagePathGetters.policy_details({ policyId }),
}),
[getUrlForApp, policyId]
@@ -36,7 +37,7 @@ const AgentsPolicyLinkComponent: React.FC = ({ policyId }
if (!isModifiedEvent(event) && isLeftClickEvent(event)) {
event.preventDefault();
- return navigateToApp('fleet', {
+ return navigateToApp(PLUGIN_ID, {
path: `#` + pagePathGetters.policy_details({ policyId }),
});
}
diff --git a/x-pack/plugins/osquery/public/components/manage_integration_link.tsx b/x-pack/plugins/osquery/public/components/manage_integration_link.tsx
index 8419003f57715..b28471a907e04 100644
--- a/x-pack/plugins/osquery/public/components/manage_integration_link.tsx
+++ b/x-pack/plugins/osquery/public/components/manage_integration_link.tsx
@@ -9,6 +9,7 @@ import React, { useCallback, useMemo } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiButtonEmpty, EuiFlexItem } from '@elastic/eui';
+import { INTEGRATIONS_PLUGIN_ID } from '../../../fleet/common';
import { pagePathGetters } from '../../../fleet/public';
import { useKibana, isModifiedEvent, isLeftClickEvent } from '../common/lib/kibana';
@@ -22,12 +23,12 @@ const ManageIntegrationLinkComponent = () => {
const integrationHref = useMemo(() => {
if (osqueryIntegration) {
- return getUrlForApp('fleet', {
+ return getUrlForApp(INTEGRATIONS_PLUGIN_ID, {
path:
'#' +
pagePathGetters.integration_details_policies({
pkgkey: `${osqueryIntegration.name}-${osqueryIntegration.version}`,
- }),
+ })[1],
});
}
}, [getUrlForApp, osqueryIntegration]);
@@ -37,12 +38,12 @@ const ManageIntegrationLinkComponent = () => {
if (!isModifiedEvent(event) && isLeftClickEvent(event)) {
event.preventDefault();
if (osqueryIntegration) {
- return navigateToApp('fleet', {
+ return navigateToApp(INTEGRATIONS_PLUGIN_ID, {
path:
'#' +
pagePathGetters.integration_details_policies({
pkgkey: `${osqueryIntegration.name}-${osqueryIntegration.version}`,
- }),
+ })[1],
});
}
}
diff --git a/x-pack/plugins/osquery/public/fleet_integration/navigation_buttons.tsx b/x-pack/plugins/osquery/public/fleet_integration/navigation_buttons.tsx
index 808718c55d199..d8169c25ad929 100644
--- a/x-pack/plugins/osquery/public/fleet_integration/navigation_buttons.tsx
+++ b/x-pack/plugins/osquery/public/fleet_integration/navigation_buttons.tsx
@@ -9,16 +9,17 @@ import { EuiFlexGroup, EuiFlexItem, EuiCard, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useCallback, useMemo } from 'react';
+import { PLUGIN_ID } from '../../common';
import { useKibana, isModifiedEvent, isLeftClickEvent } from '../common/lib/kibana';
interface NavigationButtonsProps {
isDisabled?: boolean;
- integrationPolicyId?: string;
- agentPolicyId?: string;
+ integrationPolicyId?: string | undefined;
+ agentPolicyId?: string | undefined;
}
const NavigationButtonsComponent: React.FC = ({
- isDisabled,
+ isDisabled = false,
integrationPolicyId,
agentPolicyId,
}) => {
@@ -28,7 +29,7 @@ const NavigationButtonsComponent: React.FC = ({
const liveQueryHref = useMemo(
() =>
- getUrlForApp('osquery', {
+ getUrlForApp(PLUGIN_ID, {
path: agentPolicyId
? `/live_queries/new?agentPolicyId=${agentPolicyId}`
: ' `/live_queries/new',
@@ -40,7 +41,7 @@ const NavigationButtonsComponent: React.FC = ({
(event) => {
if (!isModifiedEvent(event) && isLeftClickEvent(event)) {
event.preventDefault();
- navigateToApp('osquery', {
+ navigateToApp(PLUGIN_ID, {
path: agentPolicyId
? `/live_queries/new?agentPolicyId=${agentPolicyId}`
: ' `/live_queries/new',
@@ -50,7 +51,7 @@ const NavigationButtonsComponent: React.FC = ({
[agentPolicyId, navigateToApp]
);
- const scheduleQueryGroupsHref = getUrlForApp('osquery', {
+ const scheduleQueryGroupsHref = getUrlForApp(PLUGIN_ID, {
path: integrationPolicyId
? `/scheduled_query_groups/${integrationPolicyId}/edit`
: `/scheduled_query_groups`,
@@ -60,7 +61,7 @@ const NavigationButtonsComponent: React.FC = ({
(event) => {
if (!isModifiedEvent(event) && isLeftClickEvent(event)) {
event.preventDefault();
- navigateToApp('osquery', {
+ navigateToApp(PLUGIN_ID, {
path: integrationPolicyId
? `/scheduled_query_groups/${integrationPolicyId}/edit`
: `/scheduled_query_groups`,
diff --git a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx
index 6dfbc086c394a..2305df807f1c8 100644
--- a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx
+++ b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx
@@ -15,8 +15,10 @@ import { i18n } from '@kbn/i18n';
import {
agentRouteService,
agentPolicyRouteService,
- PackagePolicy,
AgentPolicy,
+ PLUGIN_ID,
+ INTEGRATIONS_PLUGIN_ID,
+ NewPackagePolicy,
} from '../../../fleet/common';
import {
pagePathGetters,
@@ -27,6 +29,7 @@ import {
import { ScheduledQueryGroupQueriesTable } from '../scheduled_query_groups/scheduled_query_group_queries_table';
import { useKibana } from '../common/lib/kibana';
import { NavigationButtons } from './navigation_buttons';
+import { OsqueryManagerPackagePolicy } from '../../common/types';
/**
* Exports Osquery-specific package policy instructions
@@ -51,7 +54,7 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
const agentsLinkHref = useMemo(() => {
if (!policy?.policy_id) return '#';
- return getUrlForApp('fleet', {
+ return getUrlForApp(PLUGIN_ID, {
path:
`#` +
pagePathGetters.policy_details({ policyId: policy?.policy_id }) +
@@ -128,13 +131,13 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
replace({
state: {
onSaveNavigateTo: (newPackagePolicy) => [
- 'fleet',
+ INTEGRATIONS_PLUGIN_ID,
{
path:
'#' +
pagePathGetters.integration_policy_edit({
packagePolicyId: newPackagePolicy.id,
- }),
+ })[1],
state: {
forceRefresh: true,
},
@@ -146,7 +149,11 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
}, [editMode, replace]);
const scheduledQueryGroupTableData = useMemo(() => {
- const policyWithoutEmptyQueries = produce(newPolicy, (draft) => {
+ const policyWithoutEmptyQueries = produce<
+ NewPackagePolicy,
+ OsqueryManagerPackagePolicy,
+ OsqueryManagerPackagePolicy
+ >(newPolicy, (draft) => {
draft.inputs[0].streams = filter(['compiled_stream.id', null], draft.inputs[0].streams);
return draft;
});
@@ -205,7 +212,9 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
{editMode && scheduledQueryGroupTableData.inputs[0].streams.length ? (
-
+
) : null}
diff --git a/x-pack/plugins/osquery/public/routes/scheduled_query_groups/details/index.tsx b/x-pack/plugins/osquery/public/routes/scheduled_query_groups/details/index.tsx
index a120354261305..960de043eac6e 100644
--- a/x-pack/plugins/osquery/public/routes/scheduled_query_groups/details/index.tsx
+++ b/x-pack/plugins/osquery/public/routes/scheduled_query_groups/details/index.tsx
@@ -125,7 +125,7 @@ const ScheduledQueryGroupDetailsPageComponent = () => {
return (
- {data && }
+ {data && }
);
};
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/add_query_flyout.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/form/add_query_flyout.tsx
deleted file mode 100644
index 3879a375b857c..0000000000000
--- a/x-pack/plugins/osquery/public/scheduled_query_groups/form/add_query_flyout.tsx
+++ /dev/null
@@ -1,128 +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 {
- EuiFlyout,
- EuiTitle,
- EuiSpacer,
- EuiFlyoutBody,
- EuiFlyoutHeader,
- EuiFlyoutFooter,
- EuiPortal,
- EuiFlexGroup,
- EuiFlexItem,
- EuiButtonEmpty,
- EuiButton,
-} from '@elastic/eui';
-import React from 'react';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { i18n } from '@kbn/i18n';
-
-import { CodeEditorField } from '../../queries/form/code_editor_field';
-import { idFieldValidations, intervalFieldValidation, queryFieldValidation } from './validations';
-import { Form, useForm, FormData, getUseField, Field, FIELD_TYPES } from '../../shared_imports';
-
-const FORM_ID = 'addQueryFlyoutForm';
-
-const CommonUseField = getUseField({ component: Field });
-
-interface AddQueryFlyoutProps {
- onSave: (payload: FormData) => Promise;
- onClose: () => void;
-}
-
-const AddQueryFlyoutComponent: React.FC = ({ onSave, onClose }) => {
- const { form } = useForm({
- id: FORM_ID,
- // @ts-expect-error update types
- onSubmit: (payload, isValid) => {
- if (isValid) {
- onSave(payload);
- onClose();
- }
- },
- schema: {
- id: {
- type: FIELD_TYPES.TEXT,
- label: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.idFieldLabel', {
- defaultMessage: 'ID',
- }),
- validations: idFieldValidations.map((validator) => ({ validator })),
- },
- query: {
- type: FIELD_TYPES.TEXT,
- label: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.queryFieldLabel', {
- defaultMessage: 'Query',
- }),
- validations: [{ validator: queryFieldValidation }],
- },
- interval: {
- type: FIELD_TYPES.NUMBER,
- label: i18n.translate(
- 'xpack.osquery.scheduledQueryGroup.queryFlyoutForm.intervalFieldLabel',
- {
- defaultMessage: 'Interval (s)',
- }
- ),
- validations: [{ validator: intervalFieldValidation }],
- },
- },
- });
-
- const { submit } = form;
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export const AddQueryFlyout = React.memo(AddQueryFlyoutComponent);
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/edit_query_flyout.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/form/edit_query_flyout.tsx
deleted file mode 100644
index f44b5e45a26e5..0000000000000
--- a/x-pack/plugins/osquery/public/scheduled_query_groups/form/edit_query_flyout.tsx
+++ /dev/null
@@ -1,140 +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 {
- EuiFlyout,
- EuiTitle,
- EuiSpacer,
- EuiFlyoutBody,
- EuiFlyoutHeader,
- EuiFlyoutFooter,
- EuiPortal,
- EuiFlexGroup,
- EuiFlexItem,
- EuiButtonEmpty,
- EuiButton,
-} from '@elastic/eui';
-import React from 'react';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { i18n } from '@kbn/i18n';
-
-import { PackagePolicyInputStream } from '../../../../fleet/common';
-import { CodeEditorField } from '../../queries/form/code_editor_field';
-import { Form, useForm, getUseField, Field, FIELD_TYPES } from '../../shared_imports';
-import { idFieldValidations, intervalFieldValidation, queryFieldValidation } from './validations';
-
-const FORM_ID = 'editQueryFlyoutForm';
-
-const CommonUseField = getUseField({ component: Field });
-
-interface EditQueryFlyoutProps {
- defaultValue: PackagePolicyInputStream;
- onSave: (payload: FormData) => void;
- onClose: () => void;
-}
-
-export const EditQueryFlyout: React.FC = ({
- defaultValue,
- onSave,
- onClose,
-}) => {
- const { form } = useForm({
- id: FORM_ID,
- // @ts-expect-error update types
- onSubmit: (payload, isValid) => {
- if (isValid) {
- // @ts-expect-error update types
- onSave(payload);
- onClose();
- }
- return;
- },
- defaultValue,
- deserializer: (payload) => ({
- id: payload.vars.id.value,
- query: payload.vars.query.value,
- interval: payload.vars.interval.value,
- }),
- schema: {
- id: {
- type: FIELD_TYPES.TEXT,
- label: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.idFieldLabel', {
- defaultMessage: 'ID',
- }),
- validations: idFieldValidations.map((validator) => ({ validator })),
- },
- query: {
- type: FIELD_TYPES.TEXT,
- label: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.queryFieldLabel', {
- defaultMessage: 'Query',
- }),
- validations: [{ validator: queryFieldValidation }],
- },
- interval: {
- type: FIELD_TYPES.NUMBER,
- label: i18n.translate(
- 'xpack.osquery.scheduledQueryGroup.queryFlyoutForm.intervalFieldLabel',
- {
- defaultMessage: 'Interval (s)',
- }
- ),
- validations: [{ validator: intervalFieldValidation }],
- },
- },
- });
-
- const { submit } = form;
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx
index 8924a61d181b6..64efdf61fc735 100644
--- a/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx
@@ -24,13 +24,22 @@ import { produce } from 'immer';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import { PLUGIN_ID } from '../../../common';
+import { OsqueryManagerPackagePolicy } from '../../../common/types';
import {
AgentPolicy,
- PackagePolicy,
PackagePolicyPackage,
packagePolicyRouteService,
} from '../../../../fleet/common';
-import { Form, useForm, useFormData, getUseField, Field, FIELD_TYPES } from '../../shared_imports';
+import {
+ Form,
+ useForm,
+ useFormData,
+ getUseField,
+ Field,
+ FIELD_TYPES,
+ fieldValidators,
+} from '../../shared_imports';
import { useKibana, useRouterNavigate } from '../../common/lib/kibana';
import { PolicyIdComboBoxField } from './policy_id_combobox_field';
import { QueriesField } from './queries_field';
@@ -44,7 +53,7 @@ const FORM_ID = 'scheduledQueryForm';
const CommonUseField = getUseField({ component: Field });
interface ScheduledQueryGroupFormProps {
- defaultValue?: PackagePolicy;
+ defaultValue?: OsqueryManagerPackagePolicy;
packageInfo?: PackagePolicyPackage;
editMode?: boolean;
}
@@ -89,7 +98,7 @@ const ScheduledQueryGroupFormComponent: React.FC =
{
onSuccess: (data) => {
if (!editMode) {
- navigateToApp('osquery', { path: `scheduled_query_groups/${data.item.id}` });
+ navigateToApp(PLUGIN_ID, { path: `scheduled_query_groups/${data.item.id}` });
toasts.addSuccess(
i18n.translate('xpack.osquery.scheduledQueryGroup.form.createSuccessToastMessageText', {
defaultMessage: 'Successfully scheduled {scheduledQueryGroupName}',
@@ -101,7 +110,7 @@ const ScheduledQueryGroupFormComponent: React.FC =
return;
}
- navigateToApp('osquery', { path: `scheduled_query_groups/${data.item.id}` });
+ navigateToApp(PLUGIN_ID, { path: `scheduled_query_groups/${data.item.id}` });
toasts.addSuccess(
i18n.translate('xpack.osquery.scheduledQueryGroup.form.updateSuccessToastMessageText', {
defaultMessage: 'Successfully updated {scheduledQueryGroupName}',
@@ -118,7 +127,15 @@ const ScheduledQueryGroupFormComponent: React.FC =
}
);
- const { form } = useForm({
+ const { form } = useForm<
+ Omit & {
+ policy_id: string;
+ },
+ Omit & {
+ policy_id: string[];
+ namespace: string[];
+ }
+ >({
id: FORM_ID,
schema: {
name: {
@@ -126,6 +143,18 @@ const ScheduledQueryGroupFormComponent: React.FC =
label: i18n.translate('xpack.osquery.scheduledQueryGroup.form.nameFieldLabel', {
defaultMessage: 'Name',
}),
+ validations: [
+ {
+ validator: fieldValidators.emptyField(
+ i18n.translate(
+ 'xpack.osquery.scheduledQueryGroup.form.nameFieldRequiredErrorMessage',
+ {
+ defaultMessage: 'Name is a required field',
+ }
+ )
+ ),
+ },
+ ],
},
description: {
type: FIELD_TYPES.TEXT,
@@ -144,19 +173,35 @@ const ScheduledQueryGroupFormComponent: React.FC =
label: i18n.translate('xpack.osquery.scheduledQueryGroup.form.agentPolicyFieldLabel', {
defaultMessage: 'Agent policy',
}),
+ validations: [
+ {
+ validator: fieldValidators.emptyField(
+ i18n.translate(
+ 'xpack.osquery.scheduledQueryGroup.form.policyIdFieldRequiredErrorMessage',
+ {
+ defaultMessage: 'Agent policy is a required field',
+ }
+ )
+ ),
+ },
+ ],
},
},
- onSubmit: (payload) => {
+ onSubmit: (payload, isValid) => {
+ if (!isValid) return Promise.resolve();
const formData = produce(payload, (draft) => {
- // @ts-expect-error update types
- draft.inputs[0].streams.forEach((stream) => {
- delete stream.compiled_stream;
+ if (draft.inputs?.length) {
+ draft.inputs[0].streams?.forEach((stream) => {
+ delete stream.compiled_stream;
+
+ // we don't want to send id as null when creating the policy
+ if (stream.id == null) {
+ // @ts-expect-error update types
+ delete stream.id;
+ }
+ });
+ }
- // we don't want to send id as null when creating the policy
- if (stream.id == null) {
- delete stream.id;
- }
- });
return draft;
});
return mutateAsync(formData);
@@ -164,7 +209,6 @@ const ScheduledQueryGroupFormComponent: React.FC =
options: {
stripEmptyFields: false,
},
- // @ts-expect-error update types
deserializer: (payload) => ({
...payload,
policy_id: payload.policy_id.length ? [payload.policy_id] : [],
@@ -172,9 +216,7 @@ const ScheduledQueryGroupFormComponent: React.FC =
}),
serializer: (payload) => ({
...payload,
- // @ts-expect-error update types
policy_id: payload.policy_id[0],
- // @ts-expect-error update types
namespace: payload.namespace[0],
}),
defaultValue: merge(
@@ -182,10 +224,11 @@ const ScheduledQueryGroupFormComponent: React.FC =
name: '',
description: '',
enabled: true,
- policy_id: [],
+ policy_id: '',
namespace: 'default',
output_id: '',
- package: packageInfo,
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ package: packageInfo!,
inputs: [
{
type: 'osquery',
@@ -205,7 +248,15 @@ const ScheduledQueryGroupFormComponent: React.FC =
[defaultValue, agentPolicyOptions]
);
- const [{ policy_id: policyId }] = useFormData({ form, watch: ['policy_id'] });
+ const [
+ {
+ package: { version: integrationPackageVersion } = { version: undefined },
+ policy_id: policyId,
+ },
+ ] = useFormData({
+ form,
+ watch: ['package', 'policy_id'],
+ });
const currentPolicy = useMemo(() => {
if (!policyId) {
@@ -288,6 +339,7 @@ const ScheduledQueryGroupFormComponent: React.FC =
path="inputs"
component={QueriesField}
scheduledQueryGroupId={defaultValue?.id ?? null}
+ integrationPackageVersion={integrationPackageVersion}
/>
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/queries_field.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/form/queries_field.tsx
index 34c6eaea1c265..0718ff028e002 100644
--- a/x-pack/plugins/osquery/public/scheduled_query_groups/form/queries_field.tsx
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/form/queries_field.tsx
@@ -11,16 +11,20 @@ import { produce } from 'immer';
import React, { useCallback, useMemo, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { PackagePolicyInput, PackagePolicyInputStream } from '../../../../fleet/common';
+import {
+ OsqueryManagerPackagePolicyInputStream,
+ OsqueryManagerPackagePolicyInput,
+} from '../../../common/types';
import { OSQUERY_INTEGRATION_NAME } from '../../../common';
import { FieldHook } from '../../shared_imports';
import { ScheduledQueryGroupQueriesTable } from '../scheduled_query_group_queries_table';
-import { AddQueryFlyout } from './add_query_flyout';
-import { EditQueryFlyout } from './edit_query_flyout';
+import { QueryFlyout } from '../queries/query_flyout';
import { OsqueryPackUploader } from './pack_uploader';
+import { getSupportedPlatforms } from '../queries/platforms/helpers';
interface QueriesFieldProps {
- field: FieldHook;
+ field: FieldHook;
+ integrationPackageVersion?: string | undefined;
scheduledQueryGroupId: string;
}
@@ -28,29 +32,53 @@ interface GetNewStreamProps {
id: string;
interval: string;
query: string;
+ platform?: string | undefined;
+ version?: string | undefined;
scheduledQueryGroupId?: string;
}
-const getNewStream = ({ id, interval, query, scheduledQueryGroupId }: GetNewStreamProps) => ({
- data_stream: { type: 'logs', dataset: `${OSQUERY_INTEGRATION_NAME}.result` },
- enabled: true,
- id: scheduledQueryGroupId
- ? `osquery-${OSQUERY_INTEGRATION_NAME}.result-${scheduledQueryGroupId}`
- : null,
- vars: {
- id: { type: 'text', value: id },
- interval: {
- type: 'integer',
- value: interval,
+interface GetNewStreamReturn extends Omit {
+ id?: string | null;
+}
+
+const getNewStream = (payload: GetNewStreamProps) =>
+ produce(
+ {
+ data_stream: { type: 'logs', dataset: `${OSQUERY_INTEGRATION_NAME}.result` },
+ enabled: true,
+ id: payload.scheduledQueryGroupId
+ ? `osquery-${OSQUERY_INTEGRATION_NAME}.result-${payload.scheduledQueryGroupId}`
+ : null,
+ vars: {
+ id: { type: 'text', value: payload.id },
+ interval: {
+ type: 'integer',
+ value: payload.interval,
+ },
+ query: { type: 'text', value: payload.query },
+ },
},
- query: { type: 'text', value: query },
- },
-});
+ (draft) => {
+ if (payload.platform && draft.vars) {
+ draft.vars.platform = { type: 'text', value: payload.platform };
+ }
+ if (payload.version && draft.vars) {
+ draft.vars.version = { type: 'text', value: payload.version };
+ }
+ return draft;
+ }
+ );
-const QueriesFieldComponent: React.FC = ({ field, scheduledQueryGroupId }) => {
+const QueriesFieldComponent: React.FC = ({
+ field,
+ integrationPackageVersion,
+ scheduledQueryGroupId,
+}) => {
const [showAddQueryFlyout, setShowAddQueryFlyout] = useState(false);
const [showEditQueryFlyout, setShowEditQueryFlyout] = useState(-1);
- const [tableSelectedItems, setTableSelectedItems] = useState([]);
+ const [tableSelectedItems, setTableSelectedItems] = useState<
+ OsqueryManagerPackagePolicyInputStream[]
+ >([]);
const handleShowAddFlyout = useCallback(() => setShowAddQueryFlyout(true), []);
const handleHideAddFlyout = useCallback(() => setShowAddQueryFlyout(false), []);
@@ -59,7 +87,7 @@ const QueriesFieldComponent: React.FC = ({ field, scheduledQu
const { setValue } = field;
const handleDeleteClick = useCallback(
- (stream: PackagePolicyInputStream) => {
+ (stream: OsqueryManagerPackagePolicyInputStream) => {
const streamIndex = findIndex(field.value[0].streams, [
'vars.id.value',
stream.vars?.id.value,
@@ -79,7 +107,7 @@ const QueriesFieldComponent: React.FC = ({ field, scheduledQu
);
const handleEditClick = useCallback(
- (stream: PackagePolicyInputStream) => {
+ (stream: OsqueryManagerPackagePolicyInputStream) => {
const streamIndex = findIndex(field.value[0].streams, [
'vars.id.value',
stream.vars?.id.value,
@@ -91,39 +119,61 @@ const QueriesFieldComponent: React.FC = ({ field, scheduledQu
);
const handleEditQuery = useCallback(
- (updatedQuery) => {
- if (showEditQueryFlyout >= 0) {
- setValue(
- produce((draft) => {
- draft[0].streams[showEditQueryFlyout].vars.id.value = updatedQuery.id;
- draft[0].streams[showEditQueryFlyout].vars.interval.value = updatedQuery.interval;
- draft[0].streams[showEditQueryFlyout].vars.query.value = updatedQuery.query;
+ (updatedQuery) =>
+ new Promise((resolve) => {
+ if (showEditQueryFlyout >= 0) {
+ setValue(
+ produce((draft) => {
+ draft[0].streams[showEditQueryFlyout].vars.id.value = updatedQuery.id;
+ draft[0].streams[showEditQueryFlyout].vars.interval.value = updatedQuery.interval;
+ draft[0].streams[showEditQueryFlyout].vars.query.value = updatedQuery.query;
- return draft;
- })
- );
- }
+ if (updatedQuery.platform?.length) {
+ draft[0].streams[showEditQueryFlyout].vars.platform = {
+ type: 'text',
+ value: updatedQuery.platform,
+ };
+ } else {
+ delete draft[0].streams[showEditQueryFlyout].vars.platform;
+ }
- handleHideEditFlyout();
- },
+ if (updatedQuery.version?.length) {
+ draft[0].streams[showEditQueryFlyout].vars.version = {
+ type: 'text',
+ value: updatedQuery.version,
+ };
+ } else {
+ delete draft[0].streams[showEditQueryFlyout].vars.version;
+ }
+
+ return draft;
+ })
+ );
+ }
+
+ handleHideEditFlyout();
+ resolve();
+ }),
[handleHideEditFlyout, setValue, showEditQueryFlyout]
);
const handleAddQuery = useCallback(
- (newQuery) => {
- setValue(
- produce((draft) => {
- draft[0].streams.push(
- getNewStream({
- ...newQuery,
- scheduledQueryGroupId,
- })
- );
- return draft;
- })
- );
- handleHideAddFlyout();
- },
+ (newQuery) =>
+ new Promise((resolve) => {
+ setValue(
+ produce((draft) => {
+ draft[0].streams.push(
+ getNewStream({
+ ...newQuery,
+ scheduledQueryGroupId,
+ })
+ );
+ return draft;
+ })
+ );
+ handleHideAddFlyout();
+ resolve();
+ }),
[handleHideAddFlyout, scheduledQueryGroupId, setValue]
);
@@ -148,6 +198,8 @@ const QueriesFieldComponent: React.FC = ({ field, scheduledQu
id: newQueryId,
interval: newQuery.interval,
query: newQuery.query,
+ version: newQuery.version,
+ platform: getSupportedPlatforms(newQuery.platform),
scheduledQueryGroupId,
})
);
@@ -160,7 +212,9 @@ const QueriesFieldComponent: React.FC = ({ field, scheduledQu
[scheduledQueryGroupId, setValue]
);
- const tableData = useMemo(() => ({ inputs: field.value }), [field.value]);
+ const tableData = useMemo(() => (field.value.length ? field.value[0].streams : []), [
+ field.value,
+ ]);
return (
<>
@@ -201,12 +255,16 @@ const QueriesFieldComponent: React.FC = ({ field, scheduledQu
{ }
{showAddQueryFlyout && (
- // @ts-expect-error update types
-
+
)}
{showEditQueryFlyout != null && showEditQueryFlyout >= 0 && (
-
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/constants.ts b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/constants.ts
new file mode 100644
index 0000000000000..3345c18d07b2c
--- /dev/null
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/constants.ts
@@ -0,0 +1,72 @@
+/*
+ * 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 const ALL_OSQUERY_VERSIONS_OPTIONS = [
+ {
+ label: '4.7.0',
+ },
+ {
+ label: '4.6.0',
+ },
+ {
+ label: '4.5.1',
+ },
+ {
+ label: '4.5.0',
+ },
+ {
+ label: '4.4.0',
+ },
+ {
+ label: '4.3.0',
+ },
+ {
+ label: '4.2.0',
+ },
+ {
+ label: '4.1.2',
+ },
+ {
+ label: '4.1.1',
+ },
+ {
+ label: '4.0.2',
+ },
+ {
+ label: '3.3.2',
+ },
+ {
+ label: '3.3.0',
+ },
+ {
+ label: '3.2.6',
+ },
+ {
+ label: '3.2.4',
+ },
+ {
+ label: '2.9.0',
+ },
+ {
+ label: '2.8.0',
+ },
+ {
+ label: '2.7.0',
+ },
+ {
+ label: '2.11.2',
+ },
+ {
+ label: '2.11.0',
+ },
+ {
+ label: '2.10.2',
+ },
+ {
+ label: '2.10.0',
+ },
+];
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platform_checkbox_group_field.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platform_checkbox_group_field.tsx
new file mode 100644
index 0000000000000..4e433e9e240b1
--- /dev/null
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platform_checkbox_group_field.tsx
@@ -0,0 +1,134 @@
+/*
+ * 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 { isEmpty, pickBy } from 'lodash';
+import React, { useCallback, useMemo, useState } from 'react';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFormRow,
+ EuiCheckboxGroup,
+ EuiCheckboxGroupOption,
+} from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { FieldHook, getFieldValidityAndErrorMessage } from '../../shared_imports';
+import { PlatformIcon } from './platforms/platform_icon';
+
+interface Props {
+ field: FieldHook;
+ euiFieldProps?: Record;
+ idAria?: string;
+ [key: string]: unknown;
+}
+
+export const PlatformCheckBoxGroupField = ({
+ field,
+ euiFieldProps = {},
+ idAria,
+ ...rest
+}: Props) => {
+ const options = useMemo(
+ () => [
+ {
+ id: 'linux',
+ label: (
+
+
+
+
+
+
+
+
+ ),
+ },
+ {
+ id: 'darwin',
+ label: (
+
+
+
+
+
+
+
+
+ ),
+ },
+ {
+ id: 'windows',
+ label: (
+
+
+
+
+
+
+
+
+ ),
+ },
+ ],
+ []
+ );
+
+ const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field);
+ const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState>(
+ () =>
+ (options as EuiCheckboxGroupOption[]).reduce((acc, option) => {
+ acc[option.id] = isEmpty(field.value) ? true : field.value?.includes(option.id) ?? false;
+ return acc;
+ }, {} as Record)
+ );
+
+ const onChange = useCallback(
+ (optionId: string) => {
+ const newCheckboxIdToSelectedMap = {
+ ...checkboxIdToSelectedMap,
+ [optionId]: !checkboxIdToSelectedMap[optionId],
+ };
+ setCheckboxIdToSelectedMap(newCheckboxIdToSelectedMap);
+
+ field.setValue(() =>
+ Object.keys(pickBy(newCheckboxIdToSelectedMap, (value) => value === true)).join(',')
+ );
+ },
+ [checkboxIdToSelectedMap, field]
+ );
+
+ const describedByIds = useMemo(() => (idAria ? [idAria] : []), [idAria]);
+
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/constants.ts b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/constants.ts
new file mode 100644
index 0000000000000..4f81ed73e1e7a
--- /dev/null
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/constants.ts
@@ -0,0 +1,10 @@
+/*
+ * 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 { PlatformType } from './types';
+
+export const SUPPORTED_PLATFORMS = [PlatformType.darwin, PlatformType.linux, PlatformType.windows];
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/helpers.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/helpers.tsx
new file mode 100644
index 0000000000000..362fa5c67e6f9
--- /dev/null
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/helpers.tsx
@@ -0,0 +1,55 @@
+/*
+ * 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 { uniq } from 'lodash';
+import { SUPPORTED_PLATFORMS } from './constants';
+
+import linuxSvg from './logos/linux.svg';
+import windowsSvg from './logos/windows.svg';
+import macosSvg from './logos/macos.svg';
+import { PlatformType } from './types';
+
+export const getPlatformIconModule = (platform: string) => {
+ switch (platform) {
+ case 'darwin':
+ return macosSvg;
+ case 'linux':
+ return linuxSvg;
+ case 'windows':
+ return windowsSvg;
+ default:
+ return `${platform}`;
+ }
+};
+
+export const getSupportedPlatforms = (payload: string) => {
+ let platformArray: string[];
+ try {
+ platformArray = payload?.split(',').map((platformString) => platformString.trim());
+ } catch (e) {
+ return undefined;
+ }
+
+ if (!platformArray) return;
+
+ return uniq(
+ platformArray.reduce((acc, nextPlatform) => {
+ if (!SUPPORTED_PLATFORMS.includes(nextPlatform as PlatformType)) {
+ if (nextPlatform === 'posix') {
+ acc.push(PlatformType.darwin);
+ acc.push(PlatformType.linux);
+ }
+ if (nextPlatform === 'ubuntu') {
+ acc.push(PlatformType.linux);
+ }
+ } else {
+ acc.push(nextPlatform);
+ }
+ return acc;
+ }, [] as string[])
+ ).join(',');
+};
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/index.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/index.tsx
new file mode 100644
index 0000000000000..b8af2790c6f36
--- /dev/null
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/index.tsx
@@ -0,0 +1,50 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import React, { useEffect, useState, useMemo } from 'react';
+
+import { SUPPORTED_PLATFORMS } from './constants';
+import { PlatformIcon } from './platform_icon';
+
+interface PlatformIconsProps {
+ platform: string;
+}
+
+const PlatformIconsComponent: React.FC = ({ platform }) => {
+ const [platforms, setPlatforms] = useState(SUPPORTED_PLATFORMS);
+
+ useEffect(() => {
+ setPlatforms((prevValue) => {
+ if (platform) {
+ let platformArray: string[];
+ try {
+ platformArray = platform?.split(',').map((platformString) => platformString.trim());
+ } catch (e) {
+ return prevValue;
+ }
+ return platformArray;
+ } else {
+ return SUPPORTED_PLATFORMS;
+ }
+ });
+ }, [platform]);
+
+ const content = useMemo(
+ () =>
+ platforms.map((platformString) => (
+
+
+
+ )),
+ [platforms]
+ );
+
+ return {content} ;
+};
+
+export const PlatformIcons = React.memo(PlatformIconsComponent);
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/logos/linux.svg b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/logos/linux.svg
new file mode 100644
index 0000000000000..47358292e08a8
--- /dev/null
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/logos/linux.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/logos/macos.svg b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/logos/macos.svg
new file mode 100644
index 0000000000000..baa5930800aa9
--- /dev/null
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/logos/macos.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/logos/windows.svg b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/logos/windows.svg
new file mode 100644
index 0000000000000..0872225da3a11
--- /dev/null
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/logos/windows.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/platform_icon.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/platform_icon.tsx
new file mode 100644
index 0000000000000..1126dfd690c19
--- /dev/null
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/platform_icon.tsx
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EuiIcon } from '@elastic/eui';
+import React from 'react';
+import { getPlatformIconModule } from './helpers';
+
+interface PlatformIconProps {
+ platform: string;
+}
+
+const PlatformIconComponent: React.FC = ({ platform }) => {
+ const platformIconModule = getPlatformIconModule(platform);
+ return ;
+};
+
+export const PlatformIcon = React.memo(PlatformIconComponent);
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/types.ts b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/types.ts
new file mode 100644
index 0000000000000..94953a6a854ea
--- /dev/null
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platforms/types.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export enum PlatformType {
+ darwin = 'darwin',
+ windows = 'windows',
+ linux = 'linux',
+}
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx
new file mode 100644
index 0000000000000..62ac3a46a2d77
--- /dev/null
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx
@@ -0,0 +1,176 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import {
+ EuiCallOut,
+ EuiFlyout,
+ EuiTitle,
+ EuiSpacer,
+ EuiFlyoutBody,
+ EuiFlyoutHeader,
+ EuiFlyoutFooter,
+ EuiPortal,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonEmpty,
+ EuiButton,
+} from '@elastic/eui';
+import React, { useMemo } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { satisfies } from 'semver';
+
+import { OsqueryManagerPackagePolicyConfigRecord } from '../../../common/types';
+import { CodeEditorField } from '../../queries/form/code_editor_field';
+import { Form, getUseField, Field } from '../../shared_imports';
+import { PlatformCheckBoxGroupField } from './platform_checkbox_group_field';
+import { ALL_OSQUERY_VERSIONS_OPTIONS } from './constants';
+import {
+ UseScheduledQueryGroupQueryFormProps,
+ useScheduledQueryGroupQueryForm,
+} from './use_scheduled_query_group_query_form';
+import { ManageIntegrationLink } from '../../components/manage_integration_link';
+
+const CommonUseField = getUseField({ component: Field });
+
+interface QueryFlyoutProps {
+ defaultValue?: UseScheduledQueryGroupQueryFormProps['defaultValue'] | undefined;
+ integrationPackageVersion?: string | undefined;
+ onSave: (payload: OsqueryManagerPackagePolicyConfigRecord) => Promise;
+ onClose: () => void;
+}
+
+const QueryFlyoutComponent: React.FC = ({
+ defaultValue,
+ integrationPackageVersion,
+ onSave,
+ onClose,
+}) => {
+ const { form } = useScheduledQueryGroupQueryForm({
+ defaultValue,
+ handleSubmit: (payload, isValid) =>
+ new Promise((resolve) => {
+ if (isValid) {
+ onSave(payload);
+ onClose();
+ }
+ resolve();
+ }),
+ });
+
+ /* Platform and version fields are supported since osquer_manger@0.3.0 */
+ const isFieldSupported = useMemo(
+ () => (integrationPackageVersion ? satisfies(integrationPackageVersion, '>=0.3.0') : false),
+ [integrationPackageVersion]
+ );
+
+ const { submit } = form;
+
+ return (
+
+
+
+
+
+ {defaultValue ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ {!isFieldSupported ? (
+
+ }
+ iconType="pin"
+ >
+
+
+
+
+
+
+ ) : null}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const QueryFlyout = React.memo(QueryFlyoutComponent);
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx
new file mode 100644
index 0000000000000..344c33b419dd6
--- /dev/null
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx
@@ -0,0 +1,70 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { FIELD_TYPES } from '../../shared_imports';
+
+import { idFieldValidations, intervalFieldValidation, queryFieldValidation } from './validations';
+
+export const formSchema = {
+ id: {
+ type: FIELD_TYPES.TEXT,
+ label: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.idFieldLabel', {
+ defaultMessage: 'ID',
+ }),
+ validations: idFieldValidations.map((validator) => ({ validator })),
+ },
+ query: {
+ type: FIELD_TYPES.TEXT,
+ label: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.queryFieldLabel', {
+ defaultMessage: 'Query',
+ }),
+ validations: [{ validator: queryFieldValidation }],
+ },
+ interval: {
+ defaultValue: 3600,
+ type: FIELD_TYPES.NUMBER,
+ label: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.intervalFieldLabel', {
+ defaultMessage: 'Interval (s)',
+ }),
+ validations: [{ validator: intervalFieldValidation }],
+ },
+ platform: {
+ type: FIELD_TYPES.TEXT,
+ label: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.platformFieldLabel', {
+ defaultMessage: 'Platform',
+ }),
+ validations: [],
+ },
+ version: {
+ defaultValue: [],
+ type: FIELD_TYPES.COMBO_BOX,
+ label: ((
+
+
+
+
+
+
+
+
+
+
+ ) as unknown) as string,
+ validations: [],
+ },
+};
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/use_scheduled_query_group_query_form.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/use_scheduled_query_group_query_form.tsx
new file mode 100644
index 0000000000000..bcde5f4b970d4
--- /dev/null
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/use_scheduled_query_group_query_form.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 { isArray } from 'lodash';
+import uuid from 'uuid';
+import { produce } from 'immer';
+
+import { FormConfig, useForm } from '../../shared_imports';
+import { OsqueryManagerPackagePolicyConfigRecord } from '../../../common/types';
+import { formSchema } from './schema';
+
+const FORM_ID = 'editQueryFlyoutForm';
+
+export interface UseScheduledQueryGroupQueryFormProps {
+ defaultValue?: OsqueryManagerPackagePolicyConfigRecord | undefined;
+ handleSubmit: FormConfig<
+ OsqueryManagerPackagePolicyConfigRecord,
+ ScheduledQueryGroupFormData
+ >['onSubmit'];
+}
+
+export interface ScheduledQueryGroupFormData {
+ id: string;
+ query: string;
+ interval: number;
+ platform?: string | undefined;
+ version?: string[] | undefined;
+}
+
+export const useScheduledQueryGroupQueryForm = ({
+ defaultValue,
+ handleSubmit,
+}: UseScheduledQueryGroupQueryFormProps) =>
+ useForm({
+ id: FORM_ID + uuid.v4(),
+ onSubmit: handleSubmit,
+ options: {
+ stripEmptyFields: false,
+ },
+ defaultValue,
+ // @ts-expect-error update types
+ serializer: (payload) =>
+ produce(payload, (draft) => {
+ if (draft.platform?.split(',').length === 3) {
+ // if all platforms are checked then use undefined
+ delete draft.platform;
+ }
+ if (isArray(draft.version)) {
+ if (!draft.version.length) {
+ delete draft.version;
+ } else {
+ // @ts-expect-error update types
+ draft.version = draft.version[0];
+ }
+ }
+ return draft;
+ }),
+ deserializer: (payload) => {
+ if (!payload) return {} as ScheduledQueryGroupFormData;
+
+ return {
+ id: payload.id.value,
+ query: payload.query.value,
+ interval: parseInt(payload.interval.value, 10),
+ platform: payload.platform?.value,
+ version: payload.version?.value ? [payload.version?.value] : [],
+ };
+ },
+ schema: formSchema,
+ });
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/validations.ts b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/validations.ts
similarity index 100%
rename from x-pack/plugins/osquery/public/scheduled_query_groups/form/validations.ts
rename to x-pack/plugins/osquery/public/scheduled_query_groups/queries/validations.ts
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx
index 6f78f2c086edf..36d15587086f2 100644
--- a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx
@@ -22,9 +22,10 @@ import {
PersistedIndexPatternLayer,
PieVisualizationState,
} from '../../../lens/public';
-import { PackagePolicy, PackagePolicyInputStream } from '../../../fleet/common';
import { FilterStateStore } from '../../../../../src/plugins/data/common';
import { useKibana, isModifiedEvent, isLeftClickEvent } from '../common/lib/kibana';
+import { PlatformIcons } from './queries/platforms';
+import { OsqueryManagerPackagePolicyInputStream } from '../../common/types';
export enum ViewResultsActionButtonType {
icon = 'icon',
@@ -303,12 +304,12 @@ const ViewResultsInDiscoverActionComponent: React.FC;
+ data: OsqueryManagerPackagePolicyInputStream[];
editMode?: boolean;
- onDeleteClick?: (item: PackagePolicyInputStream) => void;
- onEditClick?: (item: PackagePolicyInputStream) => void;
- selectedItems?: PackagePolicyInputStream[];
- setSelectedItems?: (selection: PackagePolicyInputStream[]) => void;
+ onDeleteClick?: (item: OsqueryManagerPackagePolicyInputStream) => void;
+ onEditClick?: (item: OsqueryManagerPackagePolicyInputStream) => void;
+ selectedItems?: OsqueryManagerPackagePolicyInputStream[];
+ setSelectedItems?: (selection: OsqueryManagerPackagePolicyInputStream[]) => void;
}
const ScheduledQueryGroupQueriesTableComponent: React.FC = ({
@@ -320,7 +321,7 @@ const ScheduledQueryGroupQueriesTableComponent: React.FC {
const renderDeleteAction = useCallback(
- (item: PackagePolicyInputStream) => (
+ (item: OsqueryManagerPackagePolicyInputStream) => (
(
+ (item: OsqueryManagerPackagePolicyInputStream) => (
,
+ []
+ );
+
+ const renderVersionColumn = useCallback(
+ (version: string) =>
+ version
+ ? `${version}`
+ : i18n.translate('xpack.osquery.scheduledQueryGroup.queriesTable.osqueryVersionAllLabel', {
+ defaultMessage: 'ALL',
+ }),
+ []
+ );
+
const renderDiscoverResultsAction = useCallback(
(item) => (
({
sort: {
- field: 'vars.id.value' as keyof PackagePolicyInputStream,
+ field: 'vars.id.value' as keyof OsqueryManagerPackagePolicyInputStream,
direction: 'asc' as const,
},
}),
[]
);
- const itemId = useCallback((item: PackagePolicyInputStream) => get('vars.id.value', item), []);
+ const itemId = useCallback(
+ (item: OsqueryManagerPackagePolicyInputStream) => get('vars.id.value', item),
+ []
+ );
const selection = useMemo(
() => ({
@@ -477,8 +512,8 @@ const ScheduledQueryGroupQueriesTableComponent: React.FC
- items={data.inputs[0].streams}
+
+ items={data}
itemId={itemId}
columns={columns}
sorting={sorting}
diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group.ts b/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group.ts
index e0f892d0302c0..93d552b3f71f3 100644
--- a/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group.ts
+++ b/x-pack/plugins/osquery/public/scheduled_query_groups/use_scheduled_query_group.ts
@@ -8,11 +8,8 @@
import { useQuery } from 'react-query';
import { useKibana } from '../common/lib/kibana';
-import {
- GetOnePackagePolicyResponse,
- PackagePolicy,
- packagePolicyRouteService,
-} from '../../../fleet/common';
+import { GetOnePackagePolicyResponse, packagePolicyRouteService } from '../../../fleet/common';
+import { OsqueryManagerPackagePolicy } from '../../common/types';
interface UseScheduledQueryGroup {
scheduledQueryGroupId: string;
@@ -25,7 +22,11 @@ export const useScheduledQueryGroup = ({
}: UseScheduledQueryGroup) => {
const { http } = useKibana().services;
- return useQuery(
+ return useQuery<
+ Omit & { item: OsqueryManagerPackagePolicy },
+ unknown,
+ OsqueryManagerPackagePolicy
+ >(
['scheduledQueryGroup', { scheduledQueryGroupId }],
() => http.get(packagePolicyRouteService.getInfoPath(scheduledQueryGroupId)),
{
diff --git a/x-pack/plugins/osquery/public/shared_imports.ts b/x-pack/plugins/osquery/public/shared_imports.ts
index 737b4d4735777..8a569a0761656 100644
--- a/x-pack/plugins/osquery/public/shared_imports.ts
+++ b/x-pack/plugins/osquery/public/shared_imports.ts
@@ -12,6 +12,7 @@ export {
FieldValidateResponse,
FIELD_TYPES,
Form,
+ FormConfig,
FormData,
FormDataProvider,
FormHook,
diff --git a/x-pack/plugins/osquery/tsconfig.json b/x-pack/plugins/osquery/tsconfig.json
index 291b0f7c607cf..76e26c770cfe0 100644
--- a/x-pack/plugins/osquery/tsconfig.json
+++ b/x-pack/plugins/osquery/tsconfig.json
@@ -12,7 +12,8 @@
"common/**/*",
"public/**/*",
"scripts/**/*",
- "server/**/*"
+ "server/**/*",
+ "../../../typings/**/*"
],
"references": [
{ "path": "../../../src/core/tsconfig.json" },
From 264339e156dc10524144aaa10bba2357aad59d0b Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Tue, 15 Jun 2021 20:06:06 +0200
Subject: [PATCH 63/91] [Lens] Export lens save modal (#100381)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
src/plugins/presentation_util/public/mocks.ts | 26 ++
.../embedded_lens_example/public/app.tsx | 30 +-
.../embedded_lens_example/public/mount.tsx | 8 +-
x-pack/plugins/lens/public/app_plugin/app.tsx | 233 +++-------
.../lens/public/app_plugin/mounter.tsx | 173 +++-----
.../app_plugin/save_modal_container.tsx | 405 ++++++++++++++++++
.../app_plugin/shared/saved_modal_lazy.tsx | 66 +++
.../plugins/lens/public/app_plugin/types.ts | 5 +-
x-pack/plugins/lens/public/async_services.ts | 1 +
x-pack/plugins/lens/public/index.ts | 2 +
x-pack/plugins/lens/public/mocks.tsx | 7 +
x-pack/plugins/lens/public/plugin.ts | 18 +-
.../observability/public/application/types.ts | 39 ++
.../shared/exploratory_view/header/header.tsx | 129 +++---
14 files changed, 800 insertions(+), 342 deletions(-)
create mode 100644 src/plugins/presentation_util/public/mocks.ts
create mode 100644 x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx
create mode 100644 x-pack/plugins/lens/public/app_plugin/shared/saved_modal_lazy.tsx
create mode 100644 x-pack/plugins/observability/public/application/types.ts
diff --git a/src/plugins/presentation_util/public/mocks.ts b/src/plugins/presentation_util/public/mocks.ts
new file mode 100644
index 0000000000000..91c461646c280
--- /dev/null
+++ b/src/plugins/presentation_util/public/mocks.ts
@@ -0,0 +1,26 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { CoreStart } from 'kibana/public';
+import { PresentationUtilPluginStart } from './types';
+import { pluginServices } from './services';
+import { registry } from './services/kibana';
+
+const createStartContract = (coreStart: CoreStart): PresentationUtilPluginStart => {
+ pluginServices.setRegistry(registry.start({ coreStart, startPlugins: {} as any }));
+
+ const startContract: PresentationUtilPluginStart = {
+ ContextProvider: pluginServices.getContextProvider(),
+ labsService: pluginServices.getServices().labs,
+ };
+ return startContract;
+};
+
+export const presentationUtilPluginMock = {
+ createStartContract,
+};
diff --git a/x-pack/examples/embedded_lens_example/public/app.tsx b/x-pack/examples/embedded_lens_example/public/app.tsx
index 33bb1f06d045c..6a39951ad4958 100644
--- a/x-pack/examples/embedded_lens_example/public/app.tsx
+++ b/x-pack/examples/embedded_lens_example/public/app.tsx
@@ -24,6 +24,7 @@ import {
TypedLensByValueInput,
PersistedIndexPatternLayer,
XYState,
+ LensEmbeddableInput,
} from '../../../plugins/lens/public';
import { StartDependencies } from './plugin';
@@ -112,12 +113,15 @@ export const App = (props: {
}) => {
const [color, setColor] = useState('green');
const [isLoading, setIsLoading] = useState(false);
+ const [isSaveModalVisible, setIsSaveModalVisible] = useState(false);
const LensComponent = props.plugins.lens.EmbeddableComponent;
+ const LensSaveModalComponent = props.plugins.lens.SaveModalComponent;
const [time, setTime] = useState({
from: 'now-5d',
to: 'now',
});
+
return (
@@ -172,7 +176,18 @@ export const App = (props: {
setColor(newColor);
}}
>
- Edit
+ Edit in Lens
+
+
+
+ {
+ setIsSaveModalVisible(true);
+ }}
+ >
+ Save Visualization
@@ -197,6 +212,19 @@ export const App = (props: {
// call back event for on table row click event
}}
/>
+ {isSaveModalVisible && (
+ {}}
+ onClose={() => setIsSaveModalVisible(false)}
+ />
+ )}
>
) : (
This demo only works if your default index pattern is set and time based
diff --git a/x-pack/examples/embedded_lens_example/public/mount.tsx b/x-pack/examples/embedded_lens_example/public/mount.tsx
index 5cf7c25fbf160..ff1e6ef8818f0 100644
--- a/x-pack/examples/embedded_lens_example/public/mount.tsx
+++ b/x-pack/examples/embedded_lens_example/public/mount.tsx
@@ -23,7 +23,13 @@ export const mount = (coreSetup: CoreSetup) => async ({
const defaultIndexPattern = await plugins.data.indexPatterns.getDefault();
- const reactElement = ;
+ const i18nCore = core.i18n;
+
+ const reactElement = (
+
+
+
+ );
render(reactElement, element);
return () => unmountComponentAtNode(element);
};
diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx
index a439a3b5788fb..2256a05c51e12 100644
--- a/x-pack/plugins/lens/public/app_plugin/app.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/app.tsx
@@ -7,7 +7,7 @@
import './app.scss';
-import { isEqual, partition } from 'lodash';
+import { isEqual } from 'lodash';
import React, { useState, useEffect, useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import { Toast } from 'kibana/public';
@@ -18,19 +18,11 @@ import {
withNotifyOnErrors,
} from '../../../../../src/plugins/kibana_utils/public';
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
-import { checkForDuplicateTitle } from '../../../../../src/plugins/saved_objects/public';
-import { injectFilterReferences } from '../persistence';
-import { trackUiEvent } from '../lens_ui_telemetry';
-import { esFilters, syncQueryStateWithUrl } from '../../../../../src/plugins/data/public';
-import { getFullPath, APP_ID } from '../../common';
-import { LensAppProps, LensAppServices, RunSave } from './types';
+import { OnSaveProps } from '../../../../../src/plugins/saved_objects/public';
+import { syncQueryStateWithUrl } from '../../../../../src/plugins/data/public';
+import { LensAppProps, LensAppServices } from './types';
import { LensTopNavMenu } from './lens_top_nav';
-import { Document } from '../persistence';
-import { SaveModal } from './save_modal';
-import {
- LensByReferenceInput,
- LensEmbeddableInput,
-} from '../editor_frame_service/embeddable/embeddable';
+import { LensByReferenceInput } from '../editor_frame_service/embeddable';
import { EditorFrameInstance } from '../types';
import {
setState as setAppState,
@@ -39,6 +31,19 @@ import {
LensAppState,
DispatchSetState,
} from '../state_management';
+import {
+ SaveModalContainer,
+ getLastKnownDocWithoutPinnedFilters,
+ runSaveLensVisualization,
+} from './save_modal_container';
+
+export type SaveProps = Omit & {
+ returnToOrigin: boolean;
+ dashboardId?: string | null;
+ onTitleDuplicate?: OnSaveProps['onTitleDuplicate'];
+ newDescription?: string;
+ newTags?: string[];
+};
export function App({
history,
@@ -48,26 +53,23 @@ export function App({
initialInput,
incomingState,
redirectToOrigin,
- redirectToDashboard,
setHeaderActionMenu,
initialContext,
}: LensAppProps) {
+ const lensAppServices = useKibana().services;
+
const {
data,
chrome,
- overlays,
uiSettings,
application,
- stateTransfer,
notifications,
- attributeService,
- savedObjectsClient,
savedObjectsTagging,
getOriginatingAppName,
// Temporarily required until the 'by value' paradigm is default.
dashboardFeatureFlag,
- } = useKibana().services;
+ } = lensAppServices;
const dispatch = useLensDispatch();
const dispatchSetState: DispatchSetState = useCallback(
@@ -205,150 +207,39 @@ export function App({
getIsByValueMode,
application,
chrome,
- initialInput,
appState.isLinkedToOriginatingApp,
appState.persistedDoc,
]);
- const tagsIds =
- appState.persistedDoc && savedObjectsTagging
- ? savedObjectsTagging.ui.getTagIdsFromReferences(appState.persistedDoc.references)
- : [];
-
- const runSave: RunSave = async (saveProps, options) => {
- if (!lastKnownDoc) {
- return;
- }
-
- let references = lastKnownDoc.references;
- if (savedObjectsTagging) {
- references = savedObjectsTagging.ui.updateTagsReferences(
- references,
- saveProps.newTags || tagsIds
- );
- }
-
- const docToSave = {
- ...getLastKnownDocWithoutPinnedFilters(lastKnownDoc)!,
- description: saveProps.newDescription,
- title: saveProps.newTitle,
- references,
- };
-
- // Required to serialize filters in by value mode until
- // https://github.com/elastic/kibana/issues/77588 is fixed
- if (getIsByValueMode()) {
- docToSave.state.filters.forEach((filter) => {
- if (typeof filter.meta.value === 'function') {
- delete filter.meta.value;
+ const runSave = (saveProps: SaveProps, options: { saveToLibrary: boolean }) => {
+ return runSaveLensVisualization(
+ {
+ lastKnownDoc,
+ getIsByValueMode,
+ savedObjectsTagging,
+ initialInput,
+ redirectToOrigin,
+ persistedDoc: appState.persistedDoc,
+ onAppLeave,
+ redirectTo,
+ ...lensAppServices,
+ },
+ saveProps,
+ options
+ ).then(
+ (newState) => {
+ if (newState) {
+ dispatchSetState(newState);
+ setIsSaveModalVisible(false);
}
- });
- }
-
- const originalInput = saveProps.newCopyOnSave ? undefined : initialInput;
- const originalSavedObjectId = (originalInput as LensByReferenceInput)?.savedObjectId;
- if (options.saveToLibrary) {
- try {
- await checkForDuplicateTitle(
- {
- id: originalSavedObjectId,
- title: docToSave.title,
- copyOnSave: saveProps.newCopyOnSave,
- lastSavedTitle: lastKnownDoc.title,
- getEsType: () => 'lens',
- getDisplayName: () =>
- i18n.translate('xpack.lens.app.saveModalType', {
- defaultMessage: 'Lens visualization',
- }),
- },
- saveProps.isTitleDuplicateConfirmed,
- saveProps.onTitleDuplicate,
- {
- savedObjectsClient,
- overlays,
- }
- );
- } catch (e) {
- // ignore duplicate title failure, user notified in save modal
- return;
- }
- }
- try {
- const newInput = (await attributeService.wrapAttributes(
- docToSave,
- options.saveToLibrary,
- originalInput
- )) as LensEmbeddableInput;
-
- if (saveProps.returnToOrigin && redirectToOrigin) {
- // disabling the validation on app leave because the document has been saved.
- onAppLeave((actions) => {
- return actions.default();
- });
- redirectToOrigin({ input: newInput, isCopied: saveProps.newCopyOnSave });
- return;
- } else if (saveProps.dashboardId && redirectToDashboard) {
- // disabling the validation on app leave because the document has been saved.
- onAppLeave((actions) => {
- return actions.default();
- });
- redirectToDashboard(newInput, saveProps.dashboardId);
- return;
- }
-
- notifications.toasts.addSuccess(
- i18n.translate('xpack.lens.app.saveVisualization.successNotificationText', {
- defaultMessage: `Saved '{visTitle}'`,
- values: {
- visTitle: docToSave.title,
- },
- })
- );
-
- if (
- attributeService.inputIsRefType(newInput) &&
- newInput.savedObjectId !== originalSavedObjectId
- ) {
- chrome.recentlyAccessed.add(
- getFullPath(newInput.savedObjectId),
- docToSave.title,
- newInput.savedObjectId
- );
-
- dispatchSetState({ isLinkedToOriginatingApp: false });
-
- setIsSaveModalVisible(false);
- // remove editor state so the connection is still broken after reload
- stateTransfer.clearEditorState(APP_ID);
-
- redirectTo(newInput.savedObjectId);
- return;
+ },
+ () => {
+ // error is handled inside the modal
+ // so ignoring it here
}
-
- const newDoc = {
- ...docToSave,
- ...newInput,
- };
-
- dispatchSetState({
- isLinkedToOriginatingApp: false,
- persistedDoc: newDoc,
- lastKnownDoc: newDoc,
- });
-
- setIsSaveModalVisible(false);
- } catch (e) {
- // eslint-disable-next-line no-console
- console.dir(e);
- trackUiEvent('save_failed');
- setIsSaveModalVisible(false);
- }
+ );
};
- const savingToLibraryPermitted = Boolean(
- appState.isSaveable && application.capabilities.visualize.save
- );
-
return (
<>
@@ -371,21 +262,24 @@ export function App({
/>
)}
- {
setIsSaveModalVisible(false);
}}
getAppNameFromId={() => getOriginatingAppName()}
lastKnownDoc={lastKnownDoc}
+ onAppLeave={onAppLeave}
+ persistedDoc={appState.persistedDoc}
+ initialInput={initialInput}
+ redirectTo={redirectTo}
+ redirectToOrigin={redirectToOrigin}
returnToOriginSwitchLabel={
getIsByValueMode() && initialInput
? i18n.translate('xpack.lens.app.updatePanel', {
@@ -419,20 +313,3 @@ const MemoizedEditorFrameWrapper = React.memo(function EditorFrameWrapper({
/>
);
});
-
-function getLastKnownDocWithoutPinnedFilters(doc?: Document) {
- if (!doc) return undefined;
- const [pinnedFilters, appFilters] = partition(
- injectFilterReferences(doc.state?.filters || [], doc.references),
- esFilters.isFilterPinned
- );
- return pinnedFilters?.length
- ? {
- ...doc,
- state: {
- ...doc.state,
- filters: appFilters,
- },
- }
- : doc;
-}
diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx
index 3e56fbb2003cb..46d2009756d2c 100644
--- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx
@@ -7,7 +7,7 @@
import React, { FC, useCallback } from 'react';
-import { AppMountParameters, CoreSetup } from 'kibana/public';
+import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public';
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom';
import { History } from 'history';
@@ -16,7 +16,7 @@ import { i18n } from '@kbn/i18n';
import { DashboardFeatureFlagConfig } from 'src/plugins/dashboard/public';
import { Provider } from 'react-redux';
-import { uniq, isEqual } from 'lodash';
+import { isEqual } from 'lodash';
import { EmbeddableEditorState } from 'src/plugins/embeddable/public';
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
@@ -26,7 +26,7 @@ import { App } from './app';
import { EditorFrameStart } from '../types';
import { addHelpMenuToAppChrome } from '../help_menu_util';
import { LensPluginStartDependencies } from '../plugin';
-import { LENS_EMBEDDABLE_TYPE, LENS_EDIT_BY_VALUE, APP_ID, getFullPath } from '../../common';
+import { LENS_EMBEDDABLE_TYPE, LENS_EDIT_BY_VALUE, APP_ID } from '../../common';
import {
LensEmbeddableInput,
LensByReferenceInput,
@@ -44,37 +44,21 @@ import {
LensRootStore,
setState,
} from '../state_management';
-import { getAllIndexPatterns, getResolvedDateRange } from '../utils';
-import { injectFilterReferences } from '../persistence';
+import { getResolvedDateRange } from '../utils';
+import { getLastKnownDoc } from './save_modal_container';
-export async function mountApp(
- core: CoreSetup,
- params: AppMountParameters,
- mountProps: {
- createEditorFrame: EditorFrameStart['createInstance'];
- getByValueFeatureFlag: () => Promise;
- attributeService: () => Promise;
- getPresentationUtilContext: () => Promise;
- }
-) {
- const {
- createEditorFrame,
- getByValueFeatureFlag,
- attributeService,
- getPresentationUtilContext,
- } = mountProps;
- const [coreStart, startDependencies] = await core.getStartServices();
+export async function getLensServices(
+ coreStart: CoreStart,
+ startDependencies: LensPluginStartDependencies,
+ attributeService: () => Promise
+): Promise {
const { data, navigation, embeddable, savedObjectsTagging } = startDependencies;
- const instance = await createEditorFrame();
const storage = new Storage(localStorage);
const stateTransfer = embeddable?.getStateTransfer();
- const historyLocationState = params.history.location.state as HistoryLocationState;
const embeddableEditorIncomingState = stateTransfer?.getIncomingEditorState(APP_ID);
- const dashboardFeatureFlag = await getByValueFeatureFlag();
-
- const lensServices: LensAppServices = {
+ return {
data,
storage,
navigation,
@@ -88,6 +72,8 @@ export async function mountApp(
application: coreStart.application,
notifications: coreStart.notifications,
savedObjectsClient: coreStart.savedObjects.client,
+ presentationUtil: startDependencies.presentationUtil,
+ dashboard: startDependencies.dashboard,
getOriginatingAppName: () => {
return embeddableEditorIncomingState?.originatingApp
? stateTransfer?.getAppNameFromId(embeddableEditorIncomingState.originatingApp)
@@ -95,8 +81,29 @@ export async function mountApp(
},
// Temporarily required until the 'by value' paradigm is default.
- dashboardFeatureFlag,
+ dashboardFeatureFlag: startDependencies.dashboard.dashboardFeatureFlagConfig,
};
+}
+
+export async function mountApp(
+ core: CoreSetup,
+ params: AppMountParameters,
+ mountProps: {
+ createEditorFrame: EditorFrameStart['createInstance'];
+ attributeService: () => Promise;
+ getPresentationUtilContext: () => Promise;
+ }
+) {
+ const { createEditorFrame, attributeService, getPresentationUtilContext } = mountProps;
+ const [coreStart, startDependencies] = await core.getStartServices();
+ const instance = await createEditorFrame();
+ const historyLocationState = params.history.location.state as HistoryLocationState;
+
+ const lensServices = await getLensServices(coreStart, startDependencies, attributeService);
+
+ const { stateTransfer, data, storage, dashboardFeatureFlag } = lensServices;
+
+ const embeddableEditorIncomingState = stateTransfer?.getIncomingEditorState(APP_ID);
addHelpMenuToAppChrome(coreStart.chrome, coreStart.docLinks);
coreStart.chrome.docTitle.change(
@@ -130,23 +137,6 @@ export async function mountApp(
}
};
- const redirectToDashboard = (embeddableInput: LensEmbeddableInput, dashboardId: string) => {
- if (!lensServices.dashboardFeatureFlag.allowByValueEmbeddables) {
- throw new Error('redirectToDashboard called with by-value embeddables disabled');
- }
-
- const state = {
- input: embeddableInput,
- type: LENS_EMBEDDABLE_TYPE,
- };
-
- const path = dashboardId === 'new' ? '#/create' : `#/view/${dashboardId}`;
- stateTransfer.navigateToWithEmbeddablePackage('dashboards', {
- state,
- path,
- });
- };
-
const redirectToOrigin = (props?: RedirectToOriginProps) => {
if (!embeddableEditorIncomingState?.originatingApp) {
throw new Error('redirectToOrigin called without an originating app');
@@ -215,7 +205,6 @@ export async function mountApp(
initialInput={initialInput}
redirectTo={redirectCallback}
redirectToOrigin={redirectToOrigin}
- redirectToDashboard={redirectToDashboard}
onAppLeave={params.onAppLeave}
setHeaderActionMenu={params.setHeaderActionMenu}
history={props.history}
@@ -299,73 +288,45 @@ export function loadDocument(
}
lensStore.dispatch(setState({ isAppLoading: true }));
- attributeService
- .unwrapAttributes(initialInput)
- .then((attributes) => {
- if (!initialInput) {
- return;
- }
- const doc = {
- ...initialInput,
- ...attributes,
- type: LENS_EMBEDDABLE_TYPE,
- };
-
- if (attributeService.inputIsRefType(initialInput)) {
- chrome.recentlyAccessed.add(
- getFullPath(initialInput.savedObjectId),
- attributes.title,
- initialInput.savedObjectId
+ getLastKnownDoc({
+ initialInput,
+ attributeService,
+ data,
+ chrome,
+ notifications,
+ }).then(
+ (newState) => {
+ if (newState) {
+ const { doc, indexPatterns } = newState;
+ const currentSessionId = data.search.session.getSessionId();
+ lensStore.dispatch(
+ setState({
+ query: doc.state.query,
+ isAppLoading: false,
+ indexPatternsForTopNav: indexPatterns,
+ lastKnownDoc: doc,
+ searchSessionId:
+ dashboardFeatureFlag.allowByValueEmbeddables &&
+ Boolean(embeddableEditorIncomingState?.originatingApp) &&
+ !(initialInput as LensByReferenceInput)?.savedObjectId &&
+ currentSessionId
+ ? currentSessionId
+ : data.search.session.start(),
+ ...(!isEqual(persistedDoc, doc) ? { persistedDoc: doc } : null),
+ })
);
+ } else {
+ redirectCallback();
}
- const indexPatternIds = uniq(
- doc.references.filter(({ type }) => type === 'index-pattern').map(({ id }) => id)
- );
- getAllIndexPatterns(indexPatternIds, data.indexPatterns)
- .then(({ indexPatterns }) => {
- // Don't overwrite any pinned filters
- data.query.filterManager.setAppFilters(
- injectFilterReferences(doc.state.filters, doc.references)
- );
- const currentSessionId = data.search.session.getSessionId();
- lensStore.dispatch(
- setState({
- query: doc.state.query,
- isAppLoading: false,
- indexPatternsForTopNav: indexPatterns,
- lastKnownDoc: doc,
- searchSessionId:
- dashboardFeatureFlag.allowByValueEmbeddables &&
- Boolean(embeddableEditorIncomingState?.originatingApp) &&
- !(initialInput as LensByReferenceInput)?.savedObjectId &&
- currentSessionId
- ? currentSessionId
- : data.search.session.start(),
- ...(!isEqual(persistedDoc, doc) ? { persistedDoc: doc } : null),
- })
- );
- })
- .catch((e) => {
- lensStore.dispatch(
- setState({
- isAppLoading: false,
- })
- );
- redirectCallback();
- });
- })
- .catch((e) => {
+ },
+ () => {
lensStore.dispatch(
setState({
isAppLoading: false,
})
);
- notifications.toasts.addDanger(
- i18n.translate('xpack.lens.app.docLoadingError', {
- defaultMessage: 'Error loading saved document',
- })
- );
redirectCallback();
- });
+ }
+ );
}
diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx
new file mode 100644
index 0000000000000..27e8031f5fb6b
--- /dev/null
+++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx
@@ -0,0 +1,405 @@
+/*
+ * 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, { useEffect, useState } from 'react';
+import { ChromeStart, NotificationsStart } from 'kibana/public';
+import { i18n } from '@kbn/i18n';
+import { partition, uniq } from 'lodash';
+import { SaveModal } from './save_modal';
+import { LensAppProps, LensAppServices } from './types';
+import type { SaveProps } from './app';
+import { Document, injectFilterReferences } from '../persistence';
+import { LensByReferenceInput, LensEmbeddableInput } from '../editor_frame_service/embeddable';
+import { LensAttributeService } from '../lens_attribute_service';
+import {
+ DataPublicPluginStart,
+ esFilters,
+ IndexPattern,
+} from '../../../../../src/plugins/data/public';
+import { APP_ID, getFullPath, LENS_EMBEDDABLE_TYPE } from '../../common';
+import { getAllIndexPatterns } from '../utils';
+import { trackUiEvent } from '../lens_ui_telemetry';
+import { checkForDuplicateTitle } from '../../../../../src/plugins/saved_objects/public';
+import { LensAppState } from '../state_management';
+
+type ExtraProps = Pick &
+ Partial>;
+
+export type SaveModalContainerProps = {
+ isVisible: boolean;
+ originatingApp?: string;
+ persistedDoc?: Document;
+ lastKnownDoc?: Document;
+ returnToOriginSwitchLabel?: string;
+ onClose: () => void;
+ onSave?: () => void;
+ runSave?: (saveProps: SaveProps, options: { saveToLibrary: boolean }) => void;
+ isSaveable?: boolean;
+ getAppNameFromId?: () => string | undefined;
+ lensServices: LensAppServices;
+} & ExtraProps;
+
+export function SaveModalContainer({
+ returnToOriginSwitchLabel,
+ onClose,
+ onSave,
+ runSave,
+ isVisible,
+ persistedDoc,
+ originatingApp,
+ initialInput,
+ redirectTo,
+ redirectToOrigin,
+ getAppNameFromId = () => undefined,
+ isSaveable = true,
+ lastKnownDoc: initLastKnowDoc,
+ lensServices,
+}: SaveModalContainerProps) {
+ const [lastKnownDoc, setLastKnownDoc] = useState(initLastKnowDoc);
+
+ const {
+ attributeService,
+ notifications,
+ data,
+ chrome,
+ savedObjectsTagging,
+ application,
+ dashboardFeatureFlag,
+ } = lensServices;
+
+ useEffect(() => {
+ setLastKnownDoc(initLastKnowDoc);
+ }, [initLastKnowDoc]);
+
+ useEffect(() => {
+ async function loadLastKnownDoc() {
+ if (initialInput && isVisible) {
+ getLastKnownDoc({
+ data,
+ initialInput,
+ chrome,
+ notifications,
+ attributeService,
+ }).then((result) => {
+ if (result) setLastKnownDoc(result.doc);
+ });
+ }
+ }
+
+ loadLastKnownDoc();
+ }, [chrome, data, initialInput, notifications, attributeService, isVisible]);
+
+ const tagsIds =
+ persistedDoc && savedObjectsTagging
+ ? savedObjectsTagging.ui.getTagIdsFromReferences(persistedDoc.references)
+ : [];
+
+ const runLensSave = (saveProps: SaveProps, options: { saveToLibrary: boolean }) => {
+ if (runSave) {
+ // inside lens, we use the function that's passed to it
+ runSave(saveProps, options);
+ } else {
+ if (attributeService && lastKnownDoc) {
+ runSaveLensVisualization(
+ {
+ ...lensServices,
+ lastKnownDoc,
+ initialInput,
+ attributeService,
+ redirectTo,
+ redirectToOrigin,
+ getIsByValueMode: () => false,
+ onAppLeave: () => {},
+ },
+ saveProps,
+ options
+ ).then(() => {
+ onSave?.();
+ onClose();
+ });
+ }
+ }
+ };
+
+ const savingToLibraryPermitted = Boolean(isSaveable && application.capabilities.visualize.save);
+
+ return (
+ {
+ runLensSave(saveProps, options);
+ }}
+ onClose={onClose}
+ getAppNameFromId={getAppNameFromId}
+ lastKnownDoc={lastKnownDoc}
+ returnToOriginSwitchLabel={returnToOriginSwitchLabel}
+ />
+ );
+}
+
+const redirectToDashboard = ({
+ embeddableInput,
+ dashboardFeatureFlag,
+ dashboardId,
+ stateTransfer,
+}: {
+ embeddableInput: LensEmbeddableInput;
+ dashboardId: string;
+ dashboardFeatureFlag: LensAppServices['dashboardFeatureFlag'];
+ stateTransfer: LensAppServices['stateTransfer'];
+}) => {
+ if (!dashboardFeatureFlag.allowByValueEmbeddables) {
+ throw new Error('redirectToDashboard called with by-value embeddables disabled');
+ }
+
+ const state = {
+ input: embeddableInput,
+ type: LENS_EMBEDDABLE_TYPE,
+ };
+
+ const path = dashboardId === 'new' ? '#/create' : `#/view/${dashboardId}`;
+ stateTransfer.navigateToWithEmbeddablePackage('dashboards', {
+ state,
+ path,
+ });
+};
+
+export const runSaveLensVisualization = async (
+ props: {
+ lastKnownDoc?: Document;
+ getIsByValueMode: () => boolean;
+ persistedDoc?: Document;
+ } & ExtraProps &
+ LensAppServices,
+ saveProps: SaveProps,
+ options: { saveToLibrary: boolean }
+): Promise | undefined> => {
+ if (!props.lastKnownDoc) {
+ return;
+ }
+
+ const {
+ chrome,
+ initialInput,
+ lastKnownDoc,
+ persistedDoc,
+ savedObjectsClient,
+ overlays,
+ notifications,
+ stateTransfer,
+ attributeService,
+ savedObjectsTagging,
+ getIsByValueMode,
+ redirectToOrigin,
+ onAppLeave,
+ redirectTo,
+ dashboardFeatureFlag,
+ } = props;
+
+ const tagsIds =
+ persistedDoc && savedObjectsTagging
+ ? savedObjectsTagging.ui.getTagIdsFromReferences(persistedDoc.references)
+ : [];
+
+ let references = lastKnownDoc.references;
+ if (savedObjectsTagging) {
+ references = savedObjectsTagging.ui.updateTagsReferences(
+ references,
+ saveProps.newTags || tagsIds
+ );
+ }
+
+ const docToSave = {
+ ...getLastKnownDocWithoutPinnedFilters(lastKnownDoc)!,
+ description: saveProps.newDescription,
+ title: saveProps.newTitle,
+ references,
+ };
+
+ // Required to serialize filters in by value mode until
+ // https://github.com/elastic/kibana/issues/77588 is fixed
+ if (getIsByValueMode()) {
+ docToSave.state.filters.forEach((filter) => {
+ if (typeof filter.meta.value === 'function') {
+ delete filter.meta.value;
+ }
+ });
+ }
+
+ const originalInput = saveProps.newCopyOnSave ? undefined : initialInput;
+ const originalSavedObjectId = (originalInput as LensByReferenceInput)?.savedObjectId;
+ if (options.saveToLibrary) {
+ try {
+ await checkForDuplicateTitle(
+ {
+ id: originalSavedObjectId,
+ title: docToSave.title,
+ copyOnSave: saveProps.newCopyOnSave,
+ lastSavedTitle: lastKnownDoc.title,
+ getEsType: () => 'lens',
+ getDisplayName: () =>
+ i18n.translate('xpack.lens.app.saveModalType', {
+ defaultMessage: 'Lens visualization',
+ }),
+ },
+ saveProps.isTitleDuplicateConfirmed,
+ saveProps.onTitleDuplicate,
+ {
+ savedObjectsClient,
+ overlays,
+ }
+ );
+ } catch (e) {
+ // ignore duplicate title failure, user notified in save modal
+ throw e;
+ }
+ }
+ try {
+ const newInput = (await attributeService.wrapAttributes(
+ docToSave,
+ options.saveToLibrary,
+ originalInput
+ )) as LensEmbeddableInput;
+
+ if (saveProps.returnToOrigin && redirectToOrigin) {
+ // disabling the validation on app leave because the document has been saved.
+ onAppLeave?.((actions) => {
+ return actions.default();
+ });
+ redirectToOrigin({ input: newInput, isCopied: saveProps.newCopyOnSave });
+ return;
+ } else if (saveProps.dashboardId) {
+ // disabling the validation on app leave because the document has been saved.
+ onAppLeave?.((actions) => {
+ return actions.default();
+ });
+ redirectToDashboard({
+ embeddableInput: newInput,
+ dashboardId: saveProps.dashboardId,
+ stateTransfer,
+ dashboardFeatureFlag,
+ });
+ return;
+ }
+
+ notifications.toasts.addSuccess(
+ i18n.translate('xpack.lens.app.saveVisualization.successNotificationText', {
+ defaultMessage: `Saved '{visTitle}'`,
+ values: {
+ visTitle: docToSave.title,
+ },
+ })
+ );
+
+ if (
+ attributeService.inputIsRefType(newInput) &&
+ newInput.savedObjectId !== originalSavedObjectId
+ ) {
+ chrome.recentlyAccessed.add(
+ getFullPath(newInput.savedObjectId),
+ docToSave.title,
+ newInput.savedObjectId
+ );
+
+ // remove editor state so the connection is still broken after reload
+ stateTransfer.clearEditorState?.(APP_ID);
+
+ redirectTo?.(newInput.savedObjectId);
+ return { isLinkedToOriginatingApp: false };
+ }
+
+ const newDoc = {
+ ...docToSave,
+ ...newInput,
+ };
+
+ return { persistedDoc: newDoc, lastKnownDoc: newDoc, isLinkedToOriginatingApp: false };
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.dir(e);
+ trackUiEvent('save_failed');
+ throw e;
+ }
+};
+
+export function getLastKnownDocWithoutPinnedFilters(doc?: Document) {
+ if (!doc) return undefined;
+ const [pinnedFilters, appFilters] = partition(
+ injectFilterReferences(doc.state?.filters || [], doc.references),
+ esFilters.isFilterPinned
+ );
+ return pinnedFilters?.length
+ ? {
+ ...doc,
+ state: {
+ ...doc.state,
+ filters: appFilters,
+ },
+ }
+ : doc;
+}
+
+export const getLastKnownDoc = async ({
+ initialInput,
+ attributeService,
+ data,
+ notifications,
+ chrome,
+}: {
+ initialInput: LensEmbeddableInput;
+ attributeService: LensAttributeService;
+ data: DataPublicPluginStart;
+ notifications: NotificationsStart;
+ chrome: ChromeStart;
+}): Promise<{ doc: Document; indexPatterns: IndexPattern[] } | undefined> => {
+ let doc: Document;
+
+ try {
+ const attributes = await attributeService.unwrapAttributes(initialInput);
+
+ doc = {
+ ...initialInput,
+ ...attributes,
+ type: LENS_EMBEDDABLE_TYPE,
+ };
+
+ if (attributeService.inputIsRefType(initialInput)) {
+ chrome.recentlyAccessed.add(
+ getFullPath(initialInput.savedObjectId),
+ attributes.title,
+ initialInput.savedObjectId
+ );
+ }
+ const indexPatternIds = uniq(
+ doc.references.filter(({ type }) => type === 'index-pattern').map(({ id }) => id)
+ );
+ const { indexPatterns } = await getAllIndexPatterns(indexPatternIds, data.indexPatterns);
+
+ // Don't overwrite any pinned filters
+ data.query.filterManager.setAppFilters(
+ injectFilterReferences(doc.state.filters, doc.references)
+ );
+ return {
+ doc,
+ indexPatterns,
+ };
+ } catch (e) {
+ notifications.toasts.addDanger(
+ i18n.translate('xpack.lens.app.docLoadingError', {
+ defaultMessage: 'Error loading saved document',
+ })
+ );
+ }
+};
+
+// eslint-disable-next-line import/no-default-export
+export default SaveModalContainer;
diff --git a/x-pack/plugins/lens/public/app_plugin/shared/saved_modal_lazy.tsx b/x-pack/plugins/lens/public/app_plugin/shared/saved_modal_lazy.tsx
new file mode 100644
index 0000000000000..f1a537fe65928
--- /dev/null
+++ b/x-pack/plugins/lens/public/app_plugin/shared/saved_modal_lazy.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 React, { Suspense, useEffect, useState } from 'react';
+
+import { EuiLoadingSpinner, EuiOverlayMask } from '@elastic/eui';
+import { CoreStart } from 'kibana/public';
+import type { SaveModalContainerProps } from '../save_modal_container';
+import type { LensAttributeService } from '../../lens_attribute_service';
+import type { LensPluginStartDependencies } from '../../plugin';
+import type { LensAppServices } from '../types';
+const SaveModal = React.lazy(() => import('../save_modal_container'));
+
+function LoadingSpinnerWithOverlay() {
+ return (
+
+
+
+ );
+}
+
+const LensSavedModalLazy = (props: SaveModalContainerProps) => {
+ return (
+ }>
+
+
+ );
+};
+
+export function getSaveModalComponent(
+ coreStart: CoreStart,
+ startDependencies: LensPluginStartDependencies,
+ attributeService: () => Promise
+) {
+ return (props: Omit) => {
+ const [lensServices, setLensServices] = useState();
+
+ useEffect(() => {
+ async function loadLensService() {
+ const { getLensServices } = await import('../../async_services');
+ const lensServicesT = await getLensServices(coreStart, startDependencies, attributeService);
+
+ setLensServices(lensServicesT);
+ }
+ loadLensService();
+ }, []);
+
+ if (!lensServices) {
+ return ;
+ }
+
+ const { ContextProvider: PresentationUtilContext } = lensServices.presentationUtil;
+
+ return (
+
+
+
+
+
+ );
+ };
+}
diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts
index 72850552723f3..d1e2d1cbdfc63 100644
--- a/x-pack/plugins/lens/public/app_plugin/types.ts
+++ b/x-pack/plugins/lens/public/app_plugin/types.ts
@@ -18,6 +18,7 @@ import {
SavedObjectsStart,
} from '../../../../../src/core/public';
import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
+import { DashboardStart } from '../../../../../src/plugins/dashboard/public';
import { LensEmbeddableInput } from '../editor_frame_service/embeddable/embeddable';
import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public';
import { LensAttributeService } from '../lens_attribute_service';
@@ -33,6 +34,7 @@ import {
EmbeddableStateTransfer,
} from '../../../../../src/plugins/embeddable/public';
import { EditorFrameInstance } from '../types';
+import { PresentationUtilPluginStart } from '../../../../../src/plugins/presentation_util/public';
export interface RedirectToOriginProps {
input?: LensEmbeddableInput;
isCopied?: boolean;
@@ -45,7 +47,6 @@ export interface LensAppProps {
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
redirectTo: (savedObjectId?: string) => void;
redirectToOrigin?: (props?: RedirectToOriginProps) => void;
- redirectToDashboard?: (input: LensEmbeddableInput, dashboardId: string) => void;
// The initial input passed in by the container when editing. Can be either by reference or by value.
initialInput?: LensEmbeddableInput;
@@ -91,6 +92,7 @@ export interface LensAppServices {
chrome: ChromeStart;
overlays: OverlayStart;
storage: IStorageWrapper;
+ dashboard: DashboardStart;
data: DataPublicPluginStart;
uiSettings: IUiSettingsClient;
application: ApplicationStart;
@@ -101,6 +103,7 @@ export interface LensAppServices {
savedObjectsClient: SavedObjectsStart['client'];
savedObjectsTagging?: SavedObjectTaggingPluginStart;
getOriginatingAppName: () => string | undefined;
+ presentationUtil: PresentationUtilPluginStart;
// Temporarily required until the 'by value' paradigm is default.
dashboardFeatureFlag: DashboardFeatureFlagConfig;
diff --git a/x-pack/plugins/lens/public/async_services.ts b/x-pack/plugins/lens/public/async_services.ts
index b0ecc412c357f..e7be295955615 100644
--- a/x-pack/plugins/lens/public/async_services.ts
+++ b/x-pack/plugins/lens/public/async_services.ts
@@ -27,3 +27,4 @@ export * from './editor_frame_service/embeddable';
export * from './app_plugin/mounter';
export * from './lens_attribute_service';
export * from './lens_ui_telemetry';
+export * from './app_plugin/save_modal_container';
diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts
index 0fdd3bf426232..98e0198b9d0fa 100644
--- a/x-pack/plugins/lens/public/index.ts
+++ b/x-pack/plugins/lens/public/index.ts
@@ -55,6 +55,8 @@ export type {
DerivativeIndexPatternColumn,
MovingAverageIndexPatternColumn,
} from './indexpattern_datasource/types';
+export type { LensEmbeddableInput } from './editor_frame_service/embeddable';
+
export { LensPublicStart } from './plugin';
export const plugin = () => new LensPlugin();
diff --git a/x-pack/plugins/lens/public/mocks.tsx b/x-pack/plugins/lens/public/mocks.tsx
index 07935bb2f241b..b35353b98a585 100644
--- a/x-pack/plugins/lens/public/mocks.tsx
+++ b/x-pack/plugins/lens/public/mocks.tsx
@@ -21,6 +21,7 @@ import { navigationPluginMock } from '../../../../src/plugins/navigation/public/
import { LensAppServices } from './app_plugin/types';
import { DOC_TYPE } from '../common';
import { DataPublicPluginStart, esFilters, UI_SETTINGS } from '../../../../src/plugins/data/public';
+import { dashboardPluginMock } from '../../../../src/plugins/dashboard/public/mocks';
import {
LensByValueInput,
LensSavedObjectAttributes,
@@ -35,6 +36,7 @@ import { EmbeddableStateTransfer } from '../../../../src/plugins/embeddable/publ
import { makeConfigureStore, getPreloadedState, LensAppState } from './state_management/index';
import { getResolvedDateRange } from './utils';
+import { presentationUtilPluginMock } from '../../../../src/plugins/presentation_util/public/mocks';
export type Start = jest.Mocked;
@@ -43,6 +45,9 @@ const createStartContract = (): Start => {
EmbeddableComponent: jest.fn(() => {
return Lens Embeddable Component ;
}),
+ SaveModalComponent: jest.fn(() => {
+ return Lens Save Modal Component ;
+ }),
canUseEditor: jest.fn(() => true),
navigateToPrefilledEditor: jest.fn(),
getXyVisTypes: jest.fn().mockReturnValue(new Promise((resolve) => resolve(visualizationTypes))),
@@ -228,6 +233,8 @@ export function makeDefaultServices(
navigation: navigationStartMock,
notifications: core.notifications,
attributeService: makeAttributeService(),
+ dashboard: dashboardPluginMock.createStartContract(),
+ presentationUtil: presentationUtilPluginMock.createStartContract(core),
savedObjectsClient: core.savedObjects.client,
dashboardFeatureFlag: { allowByValueEmbeddables: false },
stateTransfer: createEmbeddableStateTransferMock() as EmbeddableStateTransfer,
diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts
index fe225dba6f256..6d90691e2173a 100644
--- a/x-pack/plugins/lens/public/plugin.ts
+++ b/x-pack/plugins/lens/public/plugin.ts
@@ -55,6 +55,8 @@ import {
getEmbeddableComponent,
} from './editor_frame_service/embeddable/embeddable_component';
import { HeatmapVisualization } from './heatmap_visualization';
+import { getSaveModalComponent } from './app_plugin/shared/saved_modal_lazy';
+import { SaveModalContainerProps } from './app_plugin/save_modal_container';
export interface LensPluginSetupDependencies {
urlForwarding: UrlForwardingSetup;
@@ -91,6 +93,15 @@ export interface LensPublicStart {
* @experimental
*/
EmbeddableComponent: React.ComponentType;
+ /**
+ * React component which can be used to embed a Lens Visualization Save Modal Component.
+ * See `x-pack/examples/embedded_lens_example` for exemplary usage.
+ *
+ * This API might undergo breaking changes even in minor versions.
+ *
+ * @experimental
+ */
+ SaveModalComponent: React.ComponentType>;
/**
* Method which navigates to the Lens editor, loading the state specified by the `input` parameter.
* See `x-pack/examples/embedded_lens_example` for exemplary usage.
@@ -185,11 +196,6 @@ export class LensPlugin {
visualizations.registerAlias(getLensAliasConfig());
- const getByValueFeatureFlag = async () => {
- const [, deps] = await core.getStartServices();
- return deps.dashboard.dashboardFeatureFlagConfig;
- };
-
const getPresentationUtilContext = async () => {
const [, deps] = await core.getStartServices();
const { ContextProvider } = deps.presentationUtil;
@@ -214,7 +220,6 @@ export class LensPlugin {
return mountApp(core, params, {
createEditorFrame: this.createEditorFrame!,
attributeService: this.attributeService!,
- getByValueFeatureFlag,
getPresentationUtilContext,
});
},
@@ -251,6 +256,7 @@ export class LensPlugin {
return {
EmbeddableComponent: getEmbeddableComponent(startDependencies.embeddable),
+ SaveModalComponent: getSaveModalComponent(core, startDependencies, this.attributeService!),
navigateToPrefilledEditor: (input: LensEmbeddableInput, openInNewTab?: boolean) => {
// for openInNewTab, we set the time range in url via getEditPath below
if (input.timeRange && !openInNewTab) {
diff --git a/x-pack/plugins/observability/public/application/types.ts b/x-pack/plugins/observability/public/application/types.ts
new file mode 100644
index 0000000000000..09c5de1e694c8
--- /dev/null
+++ b/x-pack/plugins/observability/public/application/types.ts
@@ -0,0 +1,39 @@
+/*
+ * 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 {
+ ApplicationStart,
+ ChromeStart,
+ HttpStart,
+ IUiSettingsClient,
+ NotificationsStart,
+ OverlayStart,
+ SavedObjectsStart,
+} from 'kibana/public';
+import { EmbeddableStateTransfer } from 'src/plugins/embeddable/public';
+import { NavigationPublicPluginStart } from 'src/plugins/navigation/public';
+import { IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public';
+import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
+import { LensPublicStart } from '../../../lens/public';
+import { TriggersAndActionsUIPublicPluginStart } from '../../../triggers_actions_ui/public';
+
+export interface ObservabilityAppServices {
+ http: HttpStart;
+ chrome: ChromeStart;
+ overlays: OverlayStart;
+ storage: IStorageWrapper;
+ data: DataPublicPluginStart;
+ uiSettings: IUiSettingsClient;
+ application: ApplicationStart;
+ notifications: NotificationsStart;
+ stateTransfer: EmbeddableStateTransfer;
+ navigation: NavigationPublicPluginStart;
+ savedObjectsClient: SavedObjectsStart['client'];
+
+ triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
+ lens: LensPublicStart;
+}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx
index 3265287a7f915..3e02207e26272 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx
@@ -5,13 +5,13 @@
* 2.0.
*/
-import React from 'react';
+import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiBetaBadge, EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
-import { TypedLensByValueInput } from '../../../../../../lens/public';
+import { TypedLensByValueInput, LensEmbeddableInput } from '../../../../../../lens/public';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
-import { ObservabilityPublicPluginsStart } from '../../../../plugin';
import { DataViewLabels } from '../configurations/constants';
+import { ObservabilityAppServices } from '../../../../application/types';
import { useSeriesStorage } from '../hooks/use_series_storage';
interface Props {
@@ -20,57 +20,88 @@ interface Props {
}
export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) {
- const {
- services: { lens },
- } = useKibana();
+ const kServices = useKibana().services;
+
+ const { lens } = kServices;
const { getSeries } = useSeriesStorage();
const series = getSeries(seriesId);
+ const [isSaveOpen, setIsSaveOpen] = useState(false);
+
+ const LensSaveModalComponent = lens.SaveModalComponent;
+
return (
-
-
-
-
- {DataViewLabels[series.reportType] ??
- i18n.translate('xpack.observability.expView.heading.label', {
- defaultMessage: 'Analyze data',
- })}{' '}
-
-
-
-
-
- {
- if (lensAttributes) {
- lens.navigateToPrefilledEditor(
- {
- id: '',
- timeRange: series.time,
- attributes: lensAttributes,
- },
- true
- );
- }
- }}
- >
- {i18n.translate('xpack.observability.expView.heading.openInLens', {
- defaultMessage: 'Open in Lens',
- })}
-
-
-
+ <>
+
+
+
+
+ {DataViewLabels[series.reportType] ??
+ i18n.translate('xpack.observability.expView.heading.label', {
+ defaultMessage: 'Analyze data',
+ })}{' '}
+
+
+
+
+
+ {
+ if (lensAttributes) {
+ lens.navigateToPrefilledEditor(
+ {
+ id: '',
+ timeRange: series.time,
+ attributes: lensAttributes,
+ },
+ true
+ );
+ }
+ }}
+ >
+ {i18n.translate('xpack.observability.expView.heading.openInLens', {
+ defaultMessage: 'Open in Lens',
+ })}
+
+
+
+ {
+ if (lensAttributes) {
+ setIsSaveOpen(true);
+ }
+ }}
+ >
+ {i18n.translate('xpack.observability.expView.heading.saveLensVisualization', {
+ defaultMessage: 'Save',
+ })}
+
+
+
+
+ {isSaveOpen && lensAttributes && (
+ setIsSaveOpen(false)}
+ onSave={() => {}}
+ />
+ )}
+ >
);
}
From 35dd70b310bce92bf3edb592f9541440f3519376 Mon Sep 17 00:00:00 2001
From: "Christiane (Tina) Heiligers"
Date: Tue, 15 Jun 2021 11:46:49 -0700
Subject: [PATCH 64/91] [SO-migrations] fix flaky integration test: Uses new
fixture with default distribution (#102139)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../8.0.0_oss_sample_saved_objects.zip | Bin 82887 -> 0 bytes
...1_migrations_sample_data_saved_objects.zip | Bin 0 -> 6423883 bytes
.../integration_tests/migration.test.ts | 58 ++++++++++++++----
3 files changed, 45 insertions(+), 13 deletions(-)
delete mode 100644 src/core/server/saved_objects/migrationsv2/integration_tests/archives/8.0.0_oss_sample_saved_objects.zip
create mode 100644 src/core/server/saved_objects/migrationsv2/integration_tests/archives/8.0.0_v1_migrations_sample_data_saved_objects.zip
diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/archives/8.0.0_oss_sample_saved_objects.zip b/src/core/server/saved_objects/migrationsv2/integration_tests/archives/8.0.0_oss_sample_saved_objects.zip
deleted file mode 100644
index abb8dd2b6d491c34ca098f4b1b68646804a347fc..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 82887
zcmbriV~}QDw=I~qZJSRTm3BU9+qR8L+qUgW+qUgW+m))UuJ^m=-oE|y>2q#G?;UH$
z{xesMG3OX-#+r&U;1F;i|2zbCu}c5?`Hv4I2nvX)k*g82nkE8>48p3x8vLrkGbjd#
z;{OVv`nM2-{~2Q7;%ek-_P-)e|F=jC5SYL3zef`NTciPtmw~LCiJ83_H@g9V$<)E*
z9~57I_xYcDHTC6@Go7ns8=)l_=q6>R>l5gwXeDVMCFM`|_xIEE87nQuyQTN?
z+2?oM-kf7*w;NXdMD)eMMYfdn9{_>;&Q{t7{+A9kOn~z3lJsBRivO&?^!{^@X#a1J
zOfFXcDX^>m0EU&5m6x5RahRE&HU|RnK#xWFThrrw>gP-5$4#F84_Jy((BUf*C~{V4
zn2bM^%J5$Ou%8S%eEKG}&(;v857r1A*ji=a`lvtD&4QiShBiWe|
z0jU_NC#j|Dl>7lgCGR
z1Zczr`xh63jzT66@Rpwd*@hqPQ2#2B3~YC#5-14B&ENHZ7l`EF^oRRz1+p`;w=y?#
zab;v-a&i5qPTPM4^w0W7#{URtR$DhoGima#SYQU#vvh0vI+JChqm&U(?B+$sZCExOVo$S#iG7{e)|0b8H#4U{ksMF
zzYs5iyqoI$m6P3nDJkrKL+oN^VP|IV>SDmk`VZcJ#Qwhvepcy!=t=n>eN+CszI8HV
zVz}3FxpDdfoZ`MkK)?aU@YRcGcZLuCLycd=a6;Nh?u=mb`5Nh!zJEQwq|t%rcC0
zv(s}XZ2%B38JUHg;lrfY;iIGFprmDt42I-PNCjsrCDi$qd&)}jju?q6!>(l!4$}YdzE!UB#J?bx
z3gC%K{)P81`~QDh|G$N3_n$&M`hP-9%G4eF?+|L{1H{$MvO3H&QlYZV!X!s5G|WOn
zpu@ONPJFyN@o+F8uqm@nOiQ4s%EVJUc^jixSlQM9EKQ>&r$^M3C;!r9L&FbNmPWuD
z06;LG@fidPSI1hTiS#eJJD7xX$$#ml{Qs(mo$3Dx-E8fo@&AtQRbXd{(RRS+VbXN>
z)8SpwbkZ_e1FFz&5a#;W9kZv0E|69H!<@b3w@r*oT|)jdV1RSoaaqClBXuq
zi?$LIs{$^wEf9lMD1bU8&-ows(qtc)k6pO@D@|Js_uW%}Qw0BCo;R5P=3aXTQ?vi`
zvu5+*K}Go|n~I1i+tP13@@~f9hg|ZXxr2>O$<-oNU|j=?W|iV2nQvZnpHYK$egDz4
ze!(9Rg8tL9T&hHW{nM|s{>w_D{~N@=jg`sV^k0YJ|K3gAx}kMdPO-WC^L!kr3N8}6lg^vg0wfl~z8#>YU~a*>F6+5i#O|
zh1eZ)mQQG-A-l&J(Cg2&{P0vQtlwKYJ7RQinw@~Z88waOk#@Yu3F^X9nE4iGHd-kG
zvDTJZBjL|zv3AhHb0^vXYp8&?vqy3_n_4iyH73e@ZO@(^Vb7M0kZOmNy^(N+`c1m=J8?YD>D7uDQd8w2eMoM^aSnBQ;C^a`J~#*u6F;&ZFE^PV-e
zB@NCNpwQb|h`eU%F1OYk%B&eN#}))9$}em?kaSg_o0a13=aC`oR*X8m4q4=FcB!mI
zh-25`p?9F8UD+b|E-D#5bIhLYIYtlB=`&ARE8!aG+!)n%;fRO@z68}X{PVowv6FR{
z@(ehypDKA?pKg(Dw~FQpot?<_2XgA7_LTxhz2#@=UEHm3f4`Gf?v0h=ymk#+X1a0|
zz_W6wfGy=AXM_Ugr%(?g&TIjFp+{DSF^hCeKy0o^=7jxeY;5zM;+YxVLN}j&+Q^N5
zUhMs>kM<-DqkmZ%kM5EE!3^)WcRnt>jNd4m?g{E4Ls0SMgu&BVa1GYcONDGey2
zYBr?4UNof+%wvXtFOeVS*>~`@_WohoBM14q-EhksbP3m`L6>0xB|+x=d85*RXczU(
zo#stZl7=ryb4&?lr~wCrbK{^>9EwUWKQ>WAnLoN7y*@@^<_kX4nASghBD>~uc}&2f
z1<|R!Ir}CA1>^T8*B)gG63wte@ULVRvLB^0FBK
zX`xkwpQYLPBoPwlzp?-Ek5w3wC|6m*CG}nf@Nt{TL?I;|tpX0m2~yq&!ozH!e*b_U`hdmYlbI
zHC79$Dcr9~OB6EwidRX)%a1PS_^vVKgy|FN1DS4WsRUuI9N?UsXEnSorG}kAu&r4j
ztK0~cDAU!D;*FTbN$qGp1@19#xD^*;HUhau2egurw;;@U&g44FHylr47{j!NXcN9L
z&4d}lL75oU*ByGHow`I*UOV?X{N>N!5FQf54K55E^cq&JE6(sGg+YSi5@adY9d|Ko
zm8cZ&ICU3jGgkBv_mX)?oFgcKC?o&$GHSmCnc6Q
zRNYF=(T<`5P2)3IltFa4^GoYyH%eky>)H}FQF^QBBvZiy{K2FtA9`edAV<7tb9J65
zha0I)h8R9=So?^)CHy%>7WDq3Z0whMVXV6ouB*ISFYvSxi1kmj1B4eW+=;0Alu=wit%BjQF0?a+2m1i{uICXe5e
zLAC@9nBOKz{{dx}T|>p0c8a0jny7d4-Xsguu1VYO(J^&sVyHMW#PM=dy!!MCW$9F)7F
zm?`3_!+7RXCLOc+78OGWRY5h)lj*%?0B6NdLtBM%J<8MzU8EBtWS%-Rsdh%dfx$-j
z;={KEkv>x7qAI{x4D8P@mhj@sS27I+s{@F-e-jD@Jm)xv^pAin(ZFA@PsC#F8oQ_>
zK}y3LMG5JQ;8%k@(45N^ZB?$}P6AJ8Ne#3}G9aiuqKpWSJ4-_{CDdE+fqJWr>1I!+
zv^qz1l^`2@BIe`2xfZp%84+K(rdYEOk^Fm!`3>ZWQlQ$9ODU;8Gk++1MzjhV4>M>?
zbO7|Scm|@pDwj{h?!?fC=NZcBzi3FtDylKaB09;<5)M^>I!E>fa^ilUx7Da<>@b7}W1P0+80a{)`N;Eqkxm{4U|
zHY#vIqM)tdz!6_3#1s|MZ9d#qjtD@QbSg5an+wc5hZq)8&pgv$O++%8<#`OJ=PQ<)
zVKutpo;mZpqkEH_WB^%D%9`0-qdlm;9bzDYp?!dT^sIFY%N-uv5_=@cj)am-h@o
zgl*VRQuA>fB#|M$(G!zBP@*33L14Gm;z|Bt=o*v!0ZuKJV3MVw5S-9M%JR+6#YdI7
zNV{}(X&b7~WU5FdE52u?ynd1D`@XufRbA2)-xCsQhlE$aTqVMX%vKatJyza*nouP2JEAZ0=S@qs
z^Bk!^+yYzIYp5uMJ+CMr(85)ufF5(g{u(%v6>r#xuxgD3D=`4?dMVl`O!IbH7yj
zlF1q7=LN~ALqO(?H$y9tb#fKJ+5qi>nxF^aKZX6_SoUZV5y9VZS*TaB29;lEda0q`
zahlI;UYo^F0V!m`93&bTIBz)JXIOXmqQrHrUeeXOW~|8O8L+ZedMvC4F33G?!X