diff --git a/x-pack/plugins/maps/public/components/geometry_filter_form.js b/x-pack/plugins/maps/public/components/geometry_filter_form.js
index d5cdda3c1c324..fde07e8c16bc5 100644
--- a/x-pack/plugins/maps/public/components/geometry_filter_form.js
+++ b/x-pack/plugins/maps/public/components/geometry_filter_form.js
@@ -20,11 +20,15 @@ import { i18n } from '@kbn/i18n';
import { ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../../common/constants';
import { getEsSpatialRelationLabel } from '../../common/i18n_getters';
import { MultiIndexGeoFieldSelect } from './multi_index_geo_field_select';
+import { ActionSelect } from './action_select';
+import { ACTION_GLOBAL_APPLY_FILTER } from '../../../../../src/plugins/data/public';
export class GeometryFilterForm extends Component {
static propTypes = {
buttonLabel: PropTypes.string.isRequired,
geoFields: PropTypes.array.isRequired,
+ getFilterActions: PropTypes.func,
+ getActionContext: PropTypes.func,
intitialGeometryLabel: PropTypes.string.isRequired,
onSubmit: PropTypes.func.isRequired,
isFilterGeometryClosed: PropTypes.bool,
@@ -36,6 +40,7 @@ export class GeometryFilterForm extends Component {
};
state = {
+ actionId: ACTION_GLOBAL_APPLY_FILTER,
selectedField: this.props.geoFields.length ? this.props.geoFields[0] : undefined,
geometryLabel: this.props.intitialGeometryLabel,
relation: ES_SPATIAL_RELATIONS.INTERSECTS,
@@ -57,8 +62,13 @@ export class GeometryFilterForm extends Component {
});
};
+ _onActionIdChange = (value) => {
+ this.setState({ actionId: value });
+ };
+
_onSubmit = () => {
this.props.onSubmit({
+ actionId: this.state.actionId,
geometryLabel: this.state.geometryLabel,
indexPatternId: this.state.selectedField.indexPatternId,
geoFieldName: this.state.selectedField.geoFieldName,
@@ -134,6 +144,13 @@ export class GeometryFilterForm extends Component {
{this._renderRelationInput()}
+
+
{error}
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/feature_properties.test.js.snap b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/feature_properties.test.js.snap
index 3b3d82c92fbb7..29df06a64a3f2 100644
--- a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/feature_properties.test.js.snap
+++ b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/feature_properties.test.js.snap
@@ -1,11 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`FeatureProperties should not show filter button 1`] = `
+exports[`FeatureProperties should render 1`] = `
@@ -56,12 +60,13 @@ exports[`FeatureProperties should show error message if unable to load tooltip c
`;
-exports[`FeatureProperties should show only filter button for filterable properties 1`] = `
+exports[`FeatureProperties should show filter button for filterable properties 1`] = `
+`;
+
+exports[`FeatureProperties should show view actions button when there are available actions 1`] = `
+
+
+
+
+ prop1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ prop2
+
+
+
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/_index.scss b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/_index.scss
index fb75cc1e2db69..abd747c8fa47a 100644
--- a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/_index.scss
+++ b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/_index.scss
@@ -1,6 +1,5 @@
.mapFeatureTooltip_table {
width: 100%;
- display: block;
max-height: calc(49vh - #{$euiSizeXL * 2});
td {
@@ -8,6 +7,10 @@
}
}
+.mapFeatureTooltip_row {
+ border-bottom: 1px solid $euiColorLightestShade;
+}
+
.mapFeatureTooltip_actionLinks {
padding: $euiSizeXS;
}
@@ -20,3 +23,10 @@
max-width: $euiSizeXL * 4;
font-weight: $euiFontWeightSemiBold;
}
+
+.mapFeatureTooltip_actionsRow {
+ > span {
+ display: flex;
+ justify-content: flex-end;
+ }
+}
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js
index b0ce52b4db7ab..98267965fd30f 100644
--- a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js
+++ b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js
@@ -4,9 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { Component, Fragment } from 'react';
-import { EuiIcon } from '@elastic/eui';
-import { FormattedMessage } from '@kbn/i18n/react';
+import React, { Component } from 'react';
import { i18n } from '@kbn/i18n';
import { URL_MAX_LENGTH } from '../../../../../../../src/core/public';
@@ -95,28 +93,7 @@ export class FeatureGeometryFilterForm extends Component {
this.props.onClose();
};
- _renderHeader() {
- return (
-
-
-
-
-
-
-
-
-
- );
- }
-
- _renderForm() {
+ render() {
return (
);
}
-
- render() {
- return (
-
- {this._renderHeader()}
- {this._renderForm()}
-
- );
- }
}
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.js b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.js
index 5e2a153b2ccbf..edd501f266690 100644
--- a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.js
+++ b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.js
@@ -5,12 +5,21 @@
*/
import React from 'react';
-import { EuiCallOut, EuiLoadingSpinner, EuiTextAlign, EuiButtonIcon } from '@elastic/eui';
+import {
+ EuiCallOut,
+ EuiLoadingSpinner,
+ EuiTextAlign,
+ EuiButtonEmpty,
+ EuiIcon,
+ EuiContextMenu,
+} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { ACTION_GLOBAL_APPLY_FILTER } from '../../../../../../../src/plugins/data/public';
export class FeatureProperties extends React.Component {
state = {
properties: null,
+ actions: [],
loadPropertiesErrorMsg: null,
prevWidth: null,
prevHeight: null,
@@ -21,6 +30,7 @@ export class FeatureProperties extends React.Component {
this.prevLayerId = undefined;
this.prevFeatureId = undefined;
this._loadProperties();
+ this._loadActions();
}
componentDidUpdate() {
@@ -31,6 +41,16 @@ export class FeatureProperties extends React.Component {
this._isMounted = false;
}
+ async _loadActions() {
+ if (!this.props.getFilterActions) {
+ return;
+ }
+ const actions = await this.props.getFilterActions();
+ if (this._isMounted) {
+ this.setState({ actions });
+ }
+ }
+
_loadProperties = async () => {
this._fetchProperties({
nextFeatureId: this.props.featureId,
@@ -39,6 +59,10 @@ export class FeatureProperties extends React.Component {
});
};
+ _showFilterActions = (tooltipProperty) => {
+ this.props.showFilterActions(this._renderFilterActions(tooltipProperty));
+ };
+
_fetchProperties = async ({ nextLayerId, nextFeatureId, mbProperties }) => {
if (this.prevLayerId === nextLayerId && this.prevFeatureId === nextFeatureId) {
// do not reload same feature properties
@@ -83,35 +107,108 @@ export class FeatureProperties extends React.Component {
}
if (this._isMounted) {
- this.setState({
- properties,
- });
+ this.setState({ properties });
}
};
+ _renderFilterActions(tooltipProperty) {
+ const panel = {
+ id: 0,
+ items: this.state.actions.map((action) => {
+ const actionContext = this.props.getActionContext();
+ const iconType = action.getIconType(actionContext);
+ const name = action.getDisplayName(actionContext);
+ return {
+ name,
+ icon: iconType ? : null,
+ onClick: async () => {
+ this.props.onCloseTooltip();
+ const filters = await tooltipProperty.getESFilters();
+ this.props.addFilters(filters, action.id);
+ },
+ ['data-test-subj']: `mapFilterActionButton__${name}`,
+ };
+ }),
+ };
+
+ return (
+
+
(this._node = node)}
+ >
+
+
+
+ {tooltipProperty.getPropertyName()}
+
+
+
+
+
+
+
+ );
+ }
+
_renderFilterCell(tooltipProperty) {
if (!this.props.showFilterButtons || !tooltipProperty.isFilterable()) {
- return null;
+ return ;
}
- return (
-
- {
- this.props.onCloseTooltip();
- const filters = await tooltipProperty.getESFilters();
- this.props.addFilters(filters);
- }}
- aria-label={i18n.translate('xpack.maps.tooltip.filterOnPropertyAriaLabel', {
- defaultMessage: 'Filter on property',
- })}
- data-test-subj="mapTooltipCreateFilterButton"
- />
+ const applyFilterButton = (
+ {
+ this.props.onCloseTooltip();
+ const filters = await tooltipProperty.getESFilters();
+ this.props.addFilters(filters);
+ }}
+ aria-label={i18n.translate('xpack.maps.tooltip.filterOnPropertyAriaLabel', {
+ defaultMessage: 'Filter on property',
+ })}
+ data-test-subj="mapTooltipCreateFilterButton"
+ >
+
+
+ );
+
+ return this.state.actions.length === 0 ||
+ (this.state.actions.length === 1 &&
+ this.state.actions[0].id === ACTION_GLOBAL_APPLY_FILTER) ? (
+ {applyFilterButton}
+ ) : (
+
+
+ {applyFilterButton}
+ {
+ this._showFilterActions(tooltipProperty);
+ }}
+ aria-label={i18n.translate('xpack.maps.tooltip.viewActionsTitle', {
+ defaultMessage: 'View filter actions',
+ })}
+ data-test-subj="mapTooltipMoreActionsButton"
+ >
+
+
+
);
}
@@ -154,7 +251,7 @@ export class FeatureProperties extends React.Component {
const rows = this.state.properties.map((tooltipProperty) => {
const label = tooltipProperty.getPropertyName();
return (
-
+
{label}
{},
showFilterButtons: false,
+ getFilterActions: () => {
+ return [{ id: ACTION_GLOBAL_APPLY_FILTER }];
+ },
};
const mockTooltipProperties = [
@@ -44,10 +48,29 @@ const mockTooltipProperties = [
];
describe('FeatureProperties', () => {
- test('should not show filter button', async () => {
+ test('should render', async () => {
+ const component = shallow(
+ {
+ return mockTooltipProperties;
+ }}
+ />
+ );
+
+ // Ensure all promises resolve
+ await new Promise((resolve) => process.nextTick(resolve));
+ // Ensure the state changes are reflected
+ component.update();
+
+ expect(component).toMatchSnapshot();
+ });
+
+ test('should show filter button for filterable properties', async () => {
const component = shallow(
{
return mockTooltipProperties;
}}
@@ -62,7 +85,7 @@ describe('FeatureProperties', () => {
expect(component).toMatchSnapshot();
});
- test('should show only filter button for filterable properties', async () => {
+ test('should show view actions button when there are available actions', async () => {
const component = shallow(
{
loadFeatureProperties={() => {
return mockTooltipProperties;
}}
+ getFilterActions={() => {
+ return [{ id: 'drilldown1' }];
+ }}
/>
);
diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js
index d91bc8e803ab9..8547219b42e30 100644
--- a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js
+++ b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js
@@ -4,20 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { Fragment } from 'react';
-import { EuiLink } from '@elastic/eui';
+import React, { Component, Fragment } from 'react';
+import { EuiIcon, EuiLink } from '@elastic/eui';
import { FeatureProperties } from './feature_properties';
-import { FormattedMessage } from '@kbn/i18n/react';
import { GEO_JSON_TYPE, ES_GEO_FIELD_TYPE } from '../../../../common/constants';
import { FeatureGeometryFilterForm } from './feature_geometry_filter_form';
import { TooltipHeader } from './tooltip_header';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
const VIEWS = {
PROPERTIES_VIEW: 'PROPERTIES_VIEW',
GEOMETRY_FILTER_VIEW: 'GEOMETRY_FILTER_VIEW',
+ FILTER_ACTIONS_VIEW: 'FILTER_ACTIONS_VIEW',
};
-export class FeaturesTooltip extends React.Component {
+export class FeaturesTooltip extends Component {
state = {};
static getDerivedStateFromProps(nextProps, prevState) {
@@ -41,7 +43,11 @@ export class FeaturesTooltip extends React.Component {
};
_showPropertiesView = () => {
- this.setState({ view: VIEWS.PROPERTIES_VIEW });
+ this.setState({ view: VIEWS.PROPERTIES_VIEW, filterView: null });
+ };
+
+ _showFilterActionsView = (filterView) => {
+ this.setState({ view: VIEWS.FILTER_ACTIONS_VIEW, filterView });
};
_renderActions(geoFields) {
@@ -96,6 +102,22 @@ export class FeaturesTooltip extends React.Component {
});
};
+ _renderBackButton(label) {
+ return (
+
+
+
+
+ {label}
+
+
+ );
+ }
+
render() {
if (!this.state.currentFeature) {
return null;
@@ -109,14 +131,36 @@ export class FeaturesTooltip extends React.Component {
if (this.state.view === VIEWS.GEOMETRY_FILTER_VIEW && currentFeatureGeometry) {
return (
-
+
+ {this._renderBackButton(
+ i18n.translate('xpack.maps.tooltip.showGeometryFilterViewLinkLabel', {
+ defaultMessage: 'Filter by geometry',
+ })
+ )}
+
+
+ );
+ }
+
+ if (this.state.view === VIEWS.FILTER_ACTIONS_VIEW) {
+ return (
+
+ {this._renderBackButton(
+ i18n.translate('xpack.maps.tooltip.showAddFilterActionsViewLabel', {
+ defaultMessage: 'Filter actions',
+ })
+ )}
+ {this.state.filterView}
+
);
}
@@ -137,6 +181,9 @@ export class FeaturesTooltip extends React.Component {
showFilterButtons={!!this.props.addFilters && this.props.isLocked}
onCloseTooltip={this.props.closeTooltip}
addFilters={this.props.addFilters}
+ getFilterActions={this.props.getFilterActions}
+ getActionContext={this.props.getActionContext}
+ showFilterActions={this._showFilterActionsView}
/>
{this._renderActions(geoFields)}
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js b/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js
index 6de936fa4a8f1..49675ac6a3924 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js
+++ b/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js
@@ -62,11 +62,12 @@ export class DrawControl extends React.Component {
}
}, 0);
- _onDraw = (e) => {
+ _onDraw = async (e) => {
if (!e.features.length) {
return;
}
+ let filter;
if (this.props.drawState.drawType === DRAW_TYPE.DISTANCE) {
const circle = e.features[0];
const distanceKm = _.round(
@@ -82,7 +83,7 @@ export class DrawControl extends React.Component {
} else if (distanceKm <= 100) {
precision = 3;
}
- const filter = createDistanceFilterWithMeta({
+ filter = createDistanceFilterWithMeta({
alias: this.props.drawState.filterLabel,
distanceKm,
geoFieldName: this.props.drawState.geoFieldName,
@@ -92,17 +93,12 @@ export class DrawControl extends React.Component {
_.round(circle.properties.center[1], precision),
],
});
- this.props.addFilters([filter]);
- this.props.disableDrawState();
- return;
- }
-
- const geometry = e.features[0].geometry;
- // MapboxDraw returns coordinates with 12 decimals. Round to a more reasonable number
- roundCoordinates(geometry.coordinates);
+ } else {
+ const geometry = e.features[0].geometry;
+ // MapboxDraw returns coordinates with 12 decimals. Round to a more reasonable number
+ roundCoordinates(geometry.coordinates);
- try {
- const filter = createSpatialFilterWithGeometry({
+ filter = createSpatialFilterWithGeometry({
geometry:
this.props.drawState.drawType === DRAW_TYPE.BOUNDS
? getBoundingBoxGeometry(geometry)
@@ -113,7 +109,10 @@ export class DrawControl extends React.Component {
geometryLabel: this.props.drawState.geometryLabel,
relation: this.props.drawState.relation,
});
- this.props.addFilters([filter]);
+ }
+
+ try {
+ await this.props.addFilters([filter], this.props.drawState.actionId);
} catch (error) {
// TODO notify user why filter was not created
console.error(error);
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js b/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js
index 84a29db852539..87d6f8e1d8e71 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js
+++ b/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js
@@ -195,6 +195,8 @@ export class TooltipControl extends React.Component {
mbMap={this.props.mbMap}
layerList={this.props.layerList}
addFilters={this.props.addFilters}
+ getFilterActions={this.props.getFilterActions}
+ getActionContext={this.props.getActionContext}
renderTooltipContent={this.props.renderTooltipContent}
geoFields={this.props.geoFields}
features={features}
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js b/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js
index 6c42057680408..4cfddf0034039 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js
+++ b/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js
@@ -117,6 +117,8 @@ export class TooltipPopover extends Component {
_renderTooltipContent = () => {
const publicProps = {
addFilters: this.props.addFilters,
+ getFilterActions: this.props.getFilterActions,
+ getActionContext: this.props.getActionContext,
closeTooltip: this.props.closeTooltip,
features: this.props.features,
isLocked: this.props.isLocked,
diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/plugins/maps/public/connected_components/map/mb/view.js
index 5a38f6039ae4b..22c374aceedd5 100644
--- a/x-pack/plugins/maps/public/connected_components/map/mb/view.js
+++ b/x-pack/plugins/maps/public/connected_components/map/mb/view.js
@@ -309,6 +309,8 @@ export class MBMap extends React.Component {
diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx
index beb1eb0947c50..bf75c86ac249d 100644
--- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx
+++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx
@@ -11,6 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import uuid from 'uuid/v4';
import { Filter } from 'src/plugins/data/public';
+import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public';
// @ts-expect-error
import { MBMap } from '../map/mb';
// @ts-expect-error
@@ -35,7 +36,9 @@ import 'mapbox-gl/dist/mapbox-gl.css';
const RENDER_COMPLETE_EVENT = 'renderComplete';
interface Props {
- addFilters: ((filters: Filter[]) => void) | null;
+ addFilters: ((filters: Filter[]) => Promise) | null;
+ getFilterActions?: () => Promise;
+ getActionContext?: () => ActionExecutionContext;
areLayersLoaded: boolean;
cancelAllInFlightRequests: () => void;
exitFullScreen: () => void;
@@ -183,6 +186,8 @@ export class MapContainer extends Component {
render() {
const {
addFilters,
+ getFilterActions,
+ getActionContext,
flyoutDisplay,
isFullScreen,
exitFullScreen,
@@ -230,11 +235,18 @@ export class MapContainer extends Component {
{!this.props.hideToolbarOverlay && (
-
+
)}
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.js b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.js
index a4f85163512f7..a9dc3f822060c 100644
--- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.js
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/toolbar_overlay.js
@@ -12,14 +12,18 @@ import { FitToData } from './fit_to_data';
export class ToolbarOverlay extends React.Component {
_renderToolsControl() {
- const { addFilters, geoFields } = this.props;
+ const { addFilters, geoFields, getFilterActions, getActionContext } = this.props;
if (!addFilters || !geoFields.length) {
return null;
}
return (
-
+
);
}
diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js
index a06def086b861..017f0369e0b73 100644
--- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js
+++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js
@@ -123,6 +123,8 @@ export class ToolsControl extends Component {
className="mapDrawControl__geometryFilterForm"
buttonLabel={DRAW_SHAPE_LABEL_SHORT}
geoFields={this.props.geoFields}
+ getFilterActions={this.props.getFilterActions}
+ getActionContext={this.props.getActionContext}
intitialGeometryLabel={i18n.translate(
'xpack.maps.toolbarOverlay.drawShape.initialGeometryLabel',
{
@@ -141,6 +143,8 @@ export class ToolsControl extends Component {
className="mapDrawControl__geometryFilterForm"
buttonLabel={DRAW_BOUNDS_LABEL_SHORT}
geoFields={this.props.geoFields}
+ getFilterActions={this.props.getFilterActions}
+ getActionContext={this.props.getActionContext}
intitialGeometryLabel={i18n.translate(
'xpack.maps.toolbarOverlay.drawBounds.initialGeometryLabel',
{
@@ -161,6 +165,8 @@ export class ToolsControl extends Component {
geoFields={this.props.geoFields.filter(({ geoFieldType }) => {
return geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT;
})}
+ getFilterActions={this.props.getFilterActions}
+ getActionContext={this.props.getActionContext}
onSubmit={this._initiateDistanceDraw}
/>
),
diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
index 43ff274b1353f..1cb393bede956 100644
--- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
+++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
@@ -11,7 +11,12 @@ import { render, unmountComponentAtNode } from 'react-dom';
import { Subscription } from 'rxjs';
import { Unsubscribe } from 'redux';
import { Embeddable, IContainer } from '../../../../../src/plugins/embeddable/public';
-import { APPLY_FILTER_TRIGGER } from '../../../../../src/plugins/ui_actions/public';
+import { ACTION_GLOBAL_APPLY_FILTER } from '../../../../../src/plugins/data/public';
+import {
+ APPLY_FILTER_TRIGGER,
+ ActionExecutionContext,
+ TriggerContextMapping,
+} from '../../../../../src/plugins/ui_actions/public';
import {
esFilters,
TimeRange,
@@ -99,6 +104,10 @@ export class MapEmbeddable extends Embeddable this.onContainerStateChanged(input));
}
+ supportedTriggers(): Array {
+ return [APPLY_FILTER_TRIGGER];
+ }
+
setRenderTooltipContent = (renderTooltipContent: RenderToolTipContent) => {
this._renderTooltipContent = renderTooltipContent;
};
@@ -226,6 +235,8 @@ export class MapEmbeddable extends Embeddable
@@ -243,13 +254,36 @@ export class MapEmbeddable extends Embeddable(replaceLayerList(this._layerList));
}
- addFilters = (filters: Filter[]) => {
- getUiActions().executeTriggerActions(APPLY_FILTER_TRIGGER, {
- embeddable: this,
+ addFilters = async (filters: Filter[], actionId: string = ACTION_GLOBAL_APPLY_FILTER) => {
+ const executeContext = {
+ ...this.getActionContext(),
filters,
+ };
+ const action = getUiActions().getAction(actionId);
+ if (!action) {
+ throw new Error('Unable to apply filter, could not locate action');
+ }
+ action.execute(executeContext);
+ };
+
+ getFilterActions = async () => {
+ return await getUiActions().getTriggerCompatibleActions(APPLY_FILTER_TRIGGER, {
+ embeddable: this,
+ filters: [],
});
};
+ getActionContext = () => {
+ const trigger = getUiActions().getTrigger(APPLY_FILTER_TRIGGER);
+ if (!trigger) {
+ throw new Error('Unable to get context, could not locate trigger');
+ }
+ return {
+ embeddable: this,
+ trigger,
+ } as ActionExecutionContext;
+ };
+
destroy() {
super.destroy();
if (this._unsubscribeFromStore) {
diff --git a/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts b/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts
index 1140af0b76404..603b4fba17adb 100644
--- a/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts
+++ b/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts
@@ -23,7 +23,13 @@ export function getAnomalyDetectorsProvider({
}: SharedServicesChecks): AnomalyDetectorsProvider {
return {
anomalyDetectorsProvider(mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest) {
- const hasMlCapabilities = getHasMlCapabilities(request);
+ // APM is using this service in anomaly alert, kibana alerting doesn't provide request object
+ // So we are adding a dummy request for now
+ // TODO: Remove this once kibana alerting provides request object
+ const hasMlCapabilities =
+ request.params !== 'DummyKibanaRequest'
+ ? getHasMlCapabilities(request)
+ : (_caps: string[]) => Promise.resolve();
return {
async jobs(jobId?: string) {
isFullLicense();
diff --git a/x-pack/plugins/observability/typings/common.ts b/x-pack/plugins/observability/typings/common.ts
index 579c4fe3bbb90..19afac0c0d2b8 100644
--- a/x-pack/plugins/observability/typings/common.ts
+++ b/x-pack/plugins/observability/typings/common.ts
@@ -9,3 +9,5 @@ export type ObservabilityApp = 'infra_metrics' | 'infra_logs' | 'apm' | 'uptime'
export type PromiseReturnType = Func extends (...args: any[]) => Promise
? Value
: Func;
+
+export { Coordinates } from '../public/typings/fetch_overview_data/';
diff --git a/x-pack/plugins/security_solution/public/resolver/models/location_search.ts b/x-pack/plugins/security_solution/public/resolver/models/location_search.ts
new file mode 100644
index 0000000000000..8c21043c268d5
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/resolver/models/location_search.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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+/**
+ * The legacy `crumbEvent` and `crumbId` parameters.
+ * @deprecated
+ */
+export function breadcrumbParameters(
+ locationSearch: string,
+ resolverComponentInstanceID: string
+): { crumbEvent: string; crumbId: string } {
+ const urlSearchParams = new URLSearchParams(locationSearch);
+ const { eventKey, idKey } = parameterNames(resolverComponentInstanceID);
+ return {
+ // Use `''` for backwards compatibility with deprecated code.
+ crumbEvent: urlSearchParams.get(eventKey) ?? '',
+ crumbId: urlSearchParams.get(idKey) ?? '',
+ };
+}
+
+/**
+ * Parameter names based on the `resolverComponentInstanceID`.
+ */
+function parameterNames(
+ resolverComponentInstanceID: string
+): {
+ idKey: string;
+ eventKey: string;
+} {
+ const idKey: string = `resolver-${resolverComponentInstanceID}-id`;
+ const eventKey: string = `resolver-${resolverComponentInstanceID}-event`;
+ return {
+ idKey,
+ eventKey,
+ };
+}
diff --git a/x-pack/plugins/security_solution/public/resolver/store/actions.ts b/x-pack/plugins/security_solution/public/resolver/store/actions.ts
index 29c03215e9ff4..e03f24d78e2a2 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/actions.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/actions.ts
@@ -101,9 +101,37 @@ interface UserSelectedRelatedEventCategory {
};
}
+/**
+ * Used by `useStateSyncingActions` hook.
+ * This is dispatched when external sources provide new parameters for Resolver.
+ * When the component receives a new 'databaseDocumentID' prop, this is fired.
+ */
+interface AppReceivedNewExternalProperties {
+ type: 'appReceivedNewExternalProperties';
+ /**
+ * Defines the externally provided properties that Resolver acknowledges.
+ */
+ payload: {
+ /**
+ * the `_id` of an ES document. This defines the origin of the Resolver graph.
+ */
+ databaseDocumentID?: string;
+ /**
+ * An ID that uniquely identifies this Resolver instance from other concurrent Resolvers.
+ */
+ resolverComponentInstanceID: string;
+
+ /**
+ * The `search` part of the URL of this page.
+ */
+ locationSearch: string;
+ };
+}
+
export type ResolverAction =
| CameraAction
| DataAction
+ | AppReceivedNewExternalProperties
| UserBroughtProcessIntoView
| UserFocusedOnResolverNode
| UserSelectedResolverNode
diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/action.ts b/x-pack/plugins/security_solution/public/resolver/store/data/action.ts
index b6edf68aa7dc2..466c37d4ad5f1 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/data/action.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/data/action.ts
@@ -60,30 +60,10 @@ interface ServerReturnedRelatedEventData {
readonly payload: ResolverRelatedEvents;
}
-/**
- * Used by `useStateSyncingActions` hook.
- * This is dispatched when external sources provide new parameters for Resolver.
- * When the component receives a new 'databaseDocumentID' prop, this is fired.
- */
-interface AppReceivedNewExternalProperties {
- type: 'appReceivedNewExternalProperties';
- /**
- * Defines the externally provided properties that Resolver acknowledges.
- */
- payload: {
- /**
- * the `_id` of an ES document. This defines the origin of the Resolver graph.
- */
- databaseDocumentID?: string;
- resolverComponentInstanceID: string;
- };
-}
-
export type DataAction =
| ServerReturnedResolverData
| ServerFailedToReturnResolverData
| ServerFailedToReturnRelatedEventData
| ServerReturnedRelatedEventData
- | AppReceivedNewExternalProperties
| AppRequestedResolverData
| AppAbortedResolverDataRequest;
diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts
index 15a981d460730..dc478ede72790 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts
@@ -6,8 +6,8 @@
import * as selectors from './selectors';
import { DataState } from '../../types';
+import { ResolverAction } from '../actions';
import { dataReducer } from './reducer';
-import { DataAction } from './action';
import { createStore } from 'redux';
import {
mockTreeWithNoAncestorsAnd2Children,
@@ -20,7 +20,7 @@ import { uniquePidForProcess } from '../../models/process_event';
import { EndpointEvent } from '../../../../common/endpoint/types';
describe('data state', () => {
- let actions: DataAction[] = [];
+ let actions: ResolverAction[] = [];
/**
* Get state, given an ordered collection of actions.
@@ -68,7 +68,13 @@ describe('data state', () => {
actions = [
{
type: 'appReceivedNewExternalProperties',
- payload: { databaseDocumentID, resolverComponentInstanceID },
+ payload: {
+ databaseDocumentID,
+ resolverComponentInstanceID,
+
+ // `locationSearch` doesn't matter for this test
+ locationSearch: '',
+ },
},
];
});
@@ -120,7 +126,13 @@ describe('data state', () => {
actions = [
{
type: 'appReceivedNewExternalProperties',
- payload: { databaseDocumentID, resolverComponentInstanceID },
+ payload: {
+ databaseDocumentID,
+ resolverComponentInstanceID,
+
+ // `locationSearch` doesn't matter for this test
+ locationSearch: '',
+ },
},
{
type: 'appRequestedResolverData',
@@ -182,6 +194,8 @@ describe('data state', () => {
payload: {
databaseDocumentID: firstDatabaseDocumentID,
resolverComponentInstanceID: resolverComponentInstanceID1,
+ // `locationSearch` doesn't matter for this test
+ locationSearch: '',
},
},
// this happens when the middleware starts the request
@@ -195,6 +209,8 @@ describe('data state', () => {
payload: {
databaseDocumentID: secondDatabaseDocumentID,
resolverComponentInstanceID: resolverComponentInstanceID2,
+ // `locationSearch` doesn't matter for this test
+ locationSearch: '',
},
},
];
diff --git a/x-pack/plugins/security_solution/public/resolver/store/reducer.ts b/x-pack/plugins/security_solution/public/resolver/store/reducer.ts
index d0f9701fe944e..bf62fd0e60df8 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/reducer.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/reducer.ts
@@ -48,6 +48,13 @@ const uiReducer: Reducer = (
selectedNode: nodeID,
};
return next;
+ } else if (action.type === 'appReceivedNewExternalProperties') {
+ const next: ResolverUIState = {
+ ...state,
+ locationSearch: action.payload.locationSearch,
+ resolverComponentInstanceID: action.payload.resolverComponentInstanceID,
+ };
+ return next;
} else {
return state;
}
diff --git a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts
index f50aeed3f4d48..909a907626f30 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts
@@ -301,6 +301,15 @@ export const ariaFlowtoNodeID: (
}
);
+/**
+ * The legacy `crumbEvent` and `crumbId` parameters.
+ * @deprecated
+ */
+export const breadcrumbParameters = composeSelectors(
+ uiStateSelector,
+ uiSelectors.breadcrumbParameters
+);
+
/**
* Calls the `secondSelector` with the result of the `selector`. Use this when re-exporting a
* concern-specific selector. `selector` should return the concern-specific state.
diff --git a/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts
index 91a2cbecbc04c..5315ffb3c5fdb 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts
@@ -1,30 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { createSelector } from 'reselect';
-import { ResolverUIState } from '../../types';
-
-/**
- * id of the "current" tree node (fake-focused)
- */
-export const ariaActiveDescendant = createSelector(
- (uiState: ResolverUIState) => uiState,
- /* eslint-disable no-shadow */
- ({ ariaActiveDescendant }) => {
- return ariaActiveDescendant;
- }
-);
-
-/**
- * id of the currently "selected" tree node
- */
-export const selectedNode = createSelector(
- (uiState: ResolverUIState) => uiState,
- /* eslint-disable no-shadow */
- ({ selectedNode }: ResolverUIState) => {
- return selectedNode;
- }
-);
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { createSelector } from 'reselect';
+import { ResolverUIState } from '../../types';
+import * as locationSearchModel from '../../models/location_search';
+
+/**
+ * id of the "current" tree node (fake-focused)
+ */
+export const ariaActiveDescendant = createSelector(
+ (uiState: ResolverUIState) => uiState,
+ /* eslint-disable no-shadow */
+ ({ ariaActiveDescendant }) => {
+ return ariaActiveDescendant;
+ }
+);
+
+/**
+ * id of the currently "selected" tree node
+ */
+export const selectedNode = createSelector(
+ (uiState: ResolverUIState) => uiState,
+ /* eslint-disable no-shadow */
+ ({ selectedNode }: ResolverUIState) => {
+ return selectedNode;
+ }
+);
+
+/**
+ * The legacy `crumbEvent` and `crumbId` parameters.
+ * @deprecated
+ */
+export const breadcrumbParameters = createSelector(
+ (state: ResolverUIState) => state.locationSearch,
+ (state: ResolverUIState) => state.resolverComponentInstanceID,
+ (locationSearch, resolverComponentInstanceID) => {
+ if (locationSearch === undefined || resolverComponentInstanceID === undefined) {
+ // Equivalent to `null`
+ return {
+ crumbId: '',
+ crumbEvent: '',
+ };
+ }
+ return locationSearchModel.breadcrumbParameters(locationSearch, resolverComponentInstanceID);
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/resolver/types.ts b/x-pack/plugins/security_solution/public/resolver/types.ts
index 9ebe3fa14e842..e8304bf838e2d 100644
--- a/x-pack/plugins/security_solution/public/resolver/types.ts
+++ b/x-pack/plugins/security_solution/public/resolver/types.ts
@@ -50,6 +50,16 @@ export interface ResolverUIState {
* `nodeID` of the selected node
*/
readonly selectedNode: string | null;
+
+ /**
+ * The `search` part of the URL.
+ */
+ readonly locationSearch?: string;
+
+ /**
+ * An ID that is used to differentiate this Resolver instance from others concurrently running on the same page.
+ */
+ readonly resolverComponentInstanceID?: string;
}
/**
@@ -198,7 +208,12 @@ export interface DataState {
* The id used for the pending request, if there is one.
*/
readonly pendingRequestDatabaseDocumentID?: string;
- readonly resolverComponentInstanceID: string | undefined;
+
+ /**
+ * An ID that is used to differentiate this Resolver instance from others concurrently running on the same page.
+ * Used to prevent collisions in things like query parameters.
+ */
+ readonly resolverComponentInstanceID?: string;
/**
* The parameters and response from the last successful request.
@@ -510,8 +525,9 @@ export interface ResolverProps {
* Used as the origin of the Resolver graph.
*/
databaseDocumentID?: string;
+
/**
- * A string literal describing where in the application resolver is located.
+ * An ID that is used to differentiate this Resolver instance from others concurrently running on the same page.
* Used to prevent collisions in things like query parameters.
*/
resolverComponentInstanceID: string;
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/event_counts_for_process.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/event_counts_for_process.tsx
index c528ba547e6ae..f81dc174d8128 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/panels/event_counts_for_process.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/event_counts_for_process.tsx
@@ -12,7 +12,7 @@ import { StyledBreadcrumbs } from './panel_content_utilities';
import * as event from '../../../../common/endpoint/models/event';
import { ResolverEvent, ResolverNodeStats } from '../../../../common/endpoint/types';
-import { CrumbInfo } from '../../types';
+import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
/**
* This view gives counts for all the related events of a process grouped by related event type.
@@ -27,11 +27,9 @@ import { CrumbInfo } from '../../types';
*/
export const EventCountsForProcess = memo(function EventCountsForProcess({
processEvent,
- pushToQueryParams,
relatedStats,
}: {
processEvent: ResolverEvent;
- pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
relatedStats: ResolverNodeStats;
}) {
interface EventCountsTableView {
@@ -62,6 +60,7 @@ export const EventCountsForProcess = memo(function EventCountsForProcess({
defaultMessage: 'Events',
}
);
+ const pushToQueryParams = useReplaceBreadcrumbParameters();
const crumbs = useMemo(() => {
return [
{
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/index.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/index.tsx
index b3c4eefe5fae7..98b737de8fa59 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/panels/index.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/index.tsx
@@ -17,7 +17,6 @@ import { EventCountsForProcess } from './event_counts_for_process';
import { ProcessDetails } from './process_details';
import { ProcessListWithCounts } from './process_list_with_counts';
import { RelatedEventDetail } from './related_event_detail';
-import { useResolverQueryParams } from '../use_resolver_query_params';
/**
* The team decided to use this table to determine which breadcrumbs/view to display:
@@ -39,7 +38,7 @@ const PanelContent = memo(function PanelContent() {
const { timestamp } = useContext(SideEffectContext);
- const { pushToQueryParams, queryParams } = useResolverQueryParams();
+ const queryParams = useSelector(selectors.breadcrumbParameters);
const graphableProcesses = useSelector(selectors.graphableProcesses);
const graphableProcessEntityIds = useMemo(() => {
@@ -164,16 +163,13 @@ const PanelContent = memo(function PanelContent() {
const panelInstance = useMemo(() => {
if (panelToShow === 'processDetails') {
- return (
-
- );
+ return ;
}
if (panelToShow === 'eventCountsForProcess') {
return (
);
@@ -183,7 +179,6 @@ const PanelContent = memo(function PanelContent() {
return (
@@ -198,21 +193,13 @@ const PanelContent = memo(function PanelContent() {
);
}
// The default 'Event List' / 'List of all processes' view
- return ;
- }, [
- uiSelectedEvent,
- crumbEvent,
- crumbId,
- pushToQueryParams,
- relatedStatsForIdFromParams,
- panelToShow,
- ]);
+ return ;
+ }, [uiSelectedEvent, crumbEvent, crumbId, relatedStatsForIdFromParams, panelToShow]);
return <>{panelInstance}>;
});
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_error.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_error.tsx
index b93ef6146f1cf..4162412861f57 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_error.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_error.tsx
@@ -1,62 +1,61 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { i18n } from '@kbn/i18n';
-import { EuiSpacer, EuiText, EuiButtonEmpty } from '@elastic/eui';
-import React, { memo, useMemo } from 'react';
-import { StyledBreadcrumbs } from './panel_content_utilities';
-import { CrumbInfo } from '../../types';
-
-/**
- * Display an error in the panel when something goes wrong and give the user a way to "retreat" back to a default state.
- *
- * @param {function} pushToQueryparams A function to update the hash value in the URL to control panel state
- * @param {string} translatedErrorMessage The message to display in the panel when something goes wrong
- */
-export const PanelContentError = memo(function ({
- translatedErrorMessage,
- pushToQueryParams,
-}: {
- translatedErrorMessage: string;
- pushToQueryParams: (arg0: CrumbInfo) => unknown;
-}) {
- const crumbs = useMemo(() => {
- return [
- {
- text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.events', {
- defaultMessage: 'Events',
- }),
- onClick: () => {
- pushToQueryParams({ crumbId: '', crumbEvent: '' });
- },
- },
- {
- text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.error', {
- defaultMessage: 'Error',
- }),
- onClick: () => {},
- },
- ];
- }, [pushToQueryParams]);
- return (
- <>
-
-
- {translatedErrorMessage}
-
- {
- pushToQueryParams({ crumbId: '', crumbEvent: '' });
- }}
- >
- {i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.goBack', {
- defaultMessage: 'Click this link to return to the list of all processes.',
- })}
-
- >
- );
-});
-PanelContentError.displayName = 'TableServiceError';
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { EuiSpacer, EuiText, EuiButtonEmpty } from '@elastic/eui';
+import React, { memo, useMemo } from 'react';
+import { StyledBreadcrumbs } from './panel_content_utilities';
+import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
+
+/**
+ * Display an error in the panel when something goes wrong and give the user a way to "retreat" back to a default state.
+ *
+ * @param {function} pushToQueryparams A function to update the hash value in the URL to control panel state
+ * @param {string} translatedErrorMessage The message to display in the panel when something goes wrong
+ */
+export const PanelContentError = memo(function ({
+ translatedErrorMessage,
+}: {
+ translatedErrorMessage: string;
+}) {
+ const pushToQueryParams = useReplaceBreadcrumbParameters();
+ const crumbs = useMemo(() => {
+ return [
+ {
+ text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.events', {
+ defaultMessage: 'Events',
+ }),
+ onClick: () => {
+ pushToQueryParams({ crumbId: '', crumbEvent: '' });
+ },
+ },
+ {
+ text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.error', {
+ defaultMessage: 'Error',
+ }),
+ onClick: () => {},
+ },
+ ];
+ }, [pushToQueryParams]);
+ return (
+ <>
+
+
+ {translatedErrorMessage}
+
+ {
+ pushToQueryParams({ crumbId: '', crumbEvent: '' });
+ }}
+ >
+ {i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.goBack', {
+ defaultMessage: 'Click this link to return to the list of all processes.',
+ })}
+
+ >
+ );
+});
+PanelContentError.displayName = 'TableServiceError';
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/process_details.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/process_details.tsx
index 1ec56b8aa169a..01fa912caa866 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/panels/process_details.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/process_details.tsx
@@ -31,7 +31,8 @@ import {
import { CubeForProcess } from './cube_for_process';
import { ResolverEvent } from '../../../../common/endpoint/types';
import { useResolverTheme } from '../assets';
-import { CrumbInfo, ResolverState } from '../../types';
+import { ResolverState } from '../../types';
+import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
const StyledDescriptionList = styled(EuiDescriptionList)`
&.euiDescriptionList.euiDescriptionList--column dt.euiDescriptionList__title.desc-title {
@@ -49,10 +50,8 @@ const StyledTitle = styled('h4')`
*/
export const ProcessDetails = memo(function ProcessDetails({
processEvent,
- pushToQueryParams,
}: {
processEvent: ResolverEvent;
- pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
}) {
const processName = event.eventName(processEvent);
const entityId = event.entityId(processEvent);
@@ -127,6 +126,8 @@ export const ProcessDetails = memo(function ProcessDetails({
return processDescriptionListData;
}, [processEvent]);
+ const pushToQueryParams = useReplaceBreadcrumbParameters();
+
const crumbs = useMemo(() => {
return [
{
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/process_event_list.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/process_event_list.tsx
index a710d3ad846b3..5fe33530f05dc 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/panels/process_event_list.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/process_event_list.tsx
@@ -16,7 +16,7 @@ import { ResolverEvent, ResolverNodeStats } from '../../../../common/endpoint/ty
import * as selectors from '../../store/selectors';
import { useResolverDispatch } from '../use_resolver_dispatch';
import { RelatedEventLimitWarning } from '../limit_warnings';
-import { CrumbInfo } from '../../types';
+import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
/**
* This view presents a list of related events of a given type for a given process.
@@ -129,10 +129,8 @@ export const ProcessEventList = memo(function ProcessEventList({
processEvent,
eventType,
relatedStats,
- pushToQueryParams,
}: {
processEvent: ResolverEvent;
- pushToQueryParams: (arg0: CrumbInfo) => unknown;
eventType: string;
relatedStats: ResolverNodeStats;
}) {
@@ -169,6 +167,8 @@ export const ProcessEventList = memo(function ProcessEventList({
}
}, [relatedsReady, dispatch, processEntityId]);
+ const pushToQueryParams = useReplaceBreadcrumbParameters();
+
const waitCrumbs = useMemo(() => {
return [
{
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/process_list_with_counts.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/process_list_with_counts.tsx
index e42140feb928b..6035255824b1c 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/panels/process_list_with_counts.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/process_list_with_counts.tsx
@@ -3,6 +3,9 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+
+/* eslint-disable react/display-name */
+
import React, { memo, useContext, useCallback, useMemo } from 'react';
import {
EuiBasicTableColumn,
@@ -22,7 +25,7 @@ import { SideEffectContext } from '../side_effect_context';
import { CubeForProcess } from './cube_for_process';
import { SafeResolverEvent } from '../../../../common/endpoint/types';
import { LimitWarning } from '../limit_warnings';
-import { CrumbInfo } from '../../types';
+import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
const StyledLimitWarning = styled(LimitWarning)`
flex-flow: row wrap;
@@ -46,14 +49,8 @@ const StyledLimitWarning = styled(LimitWarning)`
/**
* The "default" view for the panel: A list of all the processes currently in the graph.
- *
- * @param {function} pushToQueryparams A function to update the hash value in the URL to control panel state
*/
-export const ProcessListWithCounts = memo(function ProcessListWithCounts({
- pushToQueryParams,
-}: {
- pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
-}) {
+export const ProcessListWithCounts = memo(() => {
interface ProcessTableView {
name?: string;
timestamp?: Date;
@@ -63,6 +60,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({
const dispatch = useResolverDispatch();
const { timestamp } = useContext(SideEffectContext);
const isProcessTerminated = useSelector(selectors.isProcessTerminated);
+ const pushToQueryParams = useReplaceBreadcrumbParameters();
const handleBringIntoViewClick = useCallback(
(processTableViewItem) => {
dispatch({
diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/related_event_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/related_event_detail.tsx
index dfafbae9c9a16..4762c615ba793 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/panels/related_event_detail.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/panels/related_event_detail.tsx
@@ -16,7 +16,8 @@ import { ResolverEvent } from '../../../../common/endpoint/types';
import * as selectors from '../../store/selectors';
import { useResolverDispatch } from '../use_resolver_dispatch';
import { PanelContentError } from './panel_content_error';
-import { CrumbInfo, ResolverState } from '../../types';
+import { ResolverState } from '../../types';
+import { useReplaceBreadcrumbParameters } from '../use_replace_breadcrumb_parameters';
// Adding some styles to prevent horizontal scrollbars, per request from UX review
const StyledDescriptionList = memo(styled(EuiDescriptionList)`
@@ -76,15 +77,13 @@ function entriesForDisplay(entries: Array<{ title: string; description: string }
* This view presents a detailed view of all the available data for a related event, split and titled by the "section"
* it appears in the underlying ResolverEvent
*/
-export const RelatedEventDetail = memo(function RelatedEventDetail({
+export const RelatedEventDetail = memo(function ({
relatedEventId,
parentEvent,
- pushToQueryParams,
countForParent,
}: {
relatedEventId: string;
parentEvent: ResolverEvent;
- pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
countForParent: number | undefined;
}) {
const processName = (parentEvent && event.eventName(parentEvent)) || '*';
@@ -130,6 +129,8 @@ export const RelatedEventDetail = memo(function RelatedEventDetail({
selectors.relatedEventDisplayInfoByEntityAndSelfId(state)(processEntityId, relatedEventId)
);
+ const pushToQueryParams = useReplaceBreadcrumbParameters();
+
const waitCrumbs = useMemo(() => {
return [
{
@@ -247,9 +248,7 @@ export const RelatedEventDetail = memo(function RelatedEventDetail({
defaultMessage: 'Related event not found.',
}
);
- return (
-
- );
+ return ;
}
return (
diff --git a/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx b/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx
index baa8ce1fcdd86..2aacc5f9176c4 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx
@@ -18,7 +18,7 @@ import { ResolverEvent, SafeResolverEvent } from '../../../common/endpoint/types
import { useResolverDispatch } from './use_resolver_dispatch';
import * as eventModel from '../../../common/endpoint/models/event';
import * as selectors from '../store/selectors';
-import { useResolverQueryParams } from './use_resolver_query_params';
+import { useReplaceBreadcrumbParameters } from './use_replace_breadcrumb_parameters';
interface StyledActionsContainer {
readonly color: string;
@@ -242,7 +242,7 @@ const UnstyledProcessEventDot = React.memo(
});
}, [dispatch, nodeID]);
- const { pushToQueryParams } = useResolverQueryParams();
+ const pushToQueryParams = useReplaceBreadcrumbParameters();
const handleClick = useCallback(() => {
if (animationTarget.current?.beginElement) {
diff --git a/x-pack/plugins/security_solution/public/resolver/view/use_resolver_query_params.ts b/x-pack/plugins/security_solution/public/resolver/view/use_replace_breadcrumb_parameters.ts
similarity index 73%
rename from x-pack/plugins/security_solution/public/resolver/view/use_resolver_query_params.ts
rename to x-pack/plugins/security_solution/public/resolver/view/use_replace_breadcrumb_parameters.ts
index b6c229181e9f7..6d819337e447d 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/use_resolver_query_params.ts
+++ b/x-pack/plugins/security_solution/public/resolver/view/use_replace_breadcrumb_parameters.ts
@@ -4,12 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { useCallback, useMemo } from 'react';
+import { useCallback } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { useQueryStringKeys } from './use_query_string_keys';
import { CrumbInfo } from '../types';
-export function useResolverQueryParams() {
+/**
+ * @deprecated
+ * Update the browser's `search` with data from `queryStringState`. The URL search parameter names
+ * will include Resolver's `resolverComponentInstanceID`.
+ */
+export function useReplaceBreadcrumbParameters(): (queryStringState: CrumbInfo) => void {
/**
* This updates the breadcrumb nav and the panel view. It's supplied to each
* panel content view to allow them to dispatch transitions to each other.
@@ -17,7 +22,7 @@ export function useResolverQueryParams() {
const history = useHistory();
const urlSearch = useLocation().search;
const { idKey, eventKey } = useQueryStringKeys();
- const pushToQueryParams = useCallback(
+ return useCallback(
(queryStringState: CrumbInfo) => {
const urlSearchParams = new URLSearchParams(urlSearch);
@@ -39,17 +44,4 @@ export function useResolverQueryParams() {
},
[history, urlSearch, idKey, eventKey]
);
- const queryParams: CrumbInfo = useMemo(() => {
- const urlSearchParams = new URLSearchParams(urlSearch);
- return {
- // Use `''` for backwards compatibility with deprecated code.
- crumbEvent: urlSearchParams.get(eventKey) ?? '',
- crumbId: urlSearchParams.get(idKey) ?? '',
- };
- }, [urlSearch, idKey, eventKey]);
-
- return {
- pushToQueryParams,
- queryParams,
- };
}
diff --git a/x-pack/plugins/security_solution/public/resolver/view/use_state_syncing_actions.ts b/x-pack/plugins/security_solution/public/resolver/view/use_state_syncing_actions.ts
index 642a054e8c519..eaba4438bb1fe 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/use_state_syncing_actions.ts
+++ b/x-pack/plugins/security_solution/public/resolver/view/use_state_syncing_actions.ts
@@ -5,6 +5,7 @@
*/
import { useLayoutEffect } from 'react';
+import { useLocation } from 'react-router-dom';
import { useResolverDispatch } from './use_resolver_dispatch';
/**
@@ -22,10 +23,11 @@ export function useStateSyncingActions({
resolverComponentInstanceID: string;
}) {
const dispatch = useResolverDispatch();
+ const locationSearch = useLocation().search;
useLayoutEffect(() => {
dispatch({
type: 'appReceivedNewExternalProperties',
- payload: { databaseDocumentID, resolverComponentInstanceID },
+ payload: { databaseDocumentID, resolverComponentInstanceID, locationSearch },
});
- }, [dispatch, databaseDocumentID, resolverComponentInstanceID]);
+ }, [dispatch, databaseDocumentID, resolverComponentInstanceID, locationSearch]);
}
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx
index 3c06af1eecb59..b7d4413e9455a 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx
@@ -15,8 +15,6 @@ import {
EuiSpacer,
EuiSwitch,
} from '@elastic/eui';
-import { FormattedMessage } from '@kbn/i18n/react';
-import { TRANSFORM_STATE } from '../../../../../../common';
import { DeleteAction } from './use_delete_action';
@@ -42,26 +40,11 @@ export const DeleteActionModal: FC = ({
}
);
const deleteModalTitle = i18n.translate('xpack.transform.transformList.deleteModalTitle', {
- defaultMessage: 'Delete {transformId}',
+ defaultMessage: 'Delete {transformId}?',
values: { transformId: items[0] && items[0].config.id },
});
const bulkDeleteModalContent = (
<>
-
- {shouldForceDelete ? (
-
- ) : (
-
- )}
-
{
@@ -100,19 +83,6 @@ export const DeleteActionModal: FC = ({
const deleteModalContent = (
<>
-
- {items[0] && items[0] && items[0].stats.state === TRANSFORM_STATE.FAILED ? (
-
- ) : (
-
- )}
-
{userCanDeleteIndex && (
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx
index 9f7dbee10cef0..ebc88918de6d5 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx
@@ -18,7 +18,7 @@ export const StartActionModal: FC = ({ closeModal, items, startAndC
values: { count: items && items.length },
});
const startModalTitle = i18n.translate('xpack.transform.transformList.startModalTitle', {
- defaultMessage: 'Start {transformId}',
+ defaultMessage: 'Start {transformId}?',
values: { transformId: items[0] && items[0].config.id },
});
@@ -40,8 +40,7 @@ export const StartActionModal: FC = ({ closeModal, items, startAndC
{i18n.translate('xpack.transform.transformList.startModalBody', {
defaultMessage:
- 'A transform will increase search and indexing load in your cluster. Please stop the transform if excessive load is experienced. Are you sure you want to start {count, plural, one {this} other {these}} {count} {count, plural, one {transform} other {transforms}}?',
- values: { count: items.length },
+ 'A transform increases search and indexing load in your cluster. If excessive load is experienced, stop the transform.',
})}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index c99980fe6205c..07b646df74b9f 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -1115,7 +1115,6 @@
"data.search.aggs.metrics.count.customLabel.help": "このアグリゲーションのカスタムラベルを表します",
"data.search.aggs.metrics.count.enabled.help": "このアグリゲーションが有効かどうかを指定します",
"data.search.aggs.metrics.count.id.help": "このアグリゲーションのID",
- "data.search.aggs.metrics.count.json.help": "アグリゲーションがElasticsearchに送信されるときに含める高度なJSON",
"data.search.aggs.metrics.count.schema.help": "このアグリゲーションで使用するスキーマ",
"data.search.aggs.metrics.countLabel": "カウント",
"data.search.aggs.metrics.countTitle": "カウント",
@@ -4742,7 +4741,6 @@
"xpack.apm.customLink.empty": "カスタムリンクが見つかりません。独自のカスタムリンク、たとえば特定のダッシュボードまたは外部リンクへのリンクをセットアップします。",
"xpack.apm.emptyMessage.noDataFoundDescription": "別の時間範囲を試すか検索フィルターをリセットしてください。",
"xpack.apm.emptyMessage.noDataFoundLabel": "データが見つかりません。",
- "xpack.apm.environment.allLabel": "すべて",
"xpack.apm.error.prompt.body": "詳細はブラウザの開発者コンソールをご確認ください。",
"xpack.apm.error.prompt.title": "申し訳ございませんが、エラーが発生しました :(",
"xpack.apm.errorGroupDetails.avgLabel": "平均",
@@ -4780,6 +4778,7 @@
"xpack.apm.fetcher.error.title": "リソースの取得中にエラーが発生しました",
"xpack.apm.fetcher.error.url": "URL",
"xpack.apm.filter.environment.label": "環境",
+ "xpack.apm.filter.environment.allLabel": "すべて",
"xpack.apm.filter.environment.notDefinedLabel": "未定義",
"xpack.apm.filter.environment.selectEnvironmentLabel": "環境を選択",
"xpack.apm.formatters.hoursTimeUnitLabel": "h",
@@ -18154,17 +18153,14 @@
"xpack.transform.transformForm.sizeNotationPlaceholder": "例: {example1}、{example2}、{example3}、{example4}",
"xpack.transform.transformList.bulkDeleteDestIndexPatternSuccessMessage": "{count}個のディスティネーションインデックス{count, plural, one {パターン} other {パターン}}を正常に削除しました。",
"xpack.transform.transformList.bulkDeleteDestIndexSuccessMessage": "{count}個のディスティネーション{count, plural, one {インデックス} other {インデックス}}を正常に削除しました。",
- "xpack.transform.transformList.bulkDeleteModalBody": "{count, plural, one {この} other {これらの}} {count} {count, plural, one {変換} other {変換}}を削除しますか?",
"xpack.transform.transformList.bulkDeleteModalTitle": "{count} 件の{count, plural, one {変換} other {変換}}を削除",
"xpack.transform.transformList.bulkDeleteTransformSuccessMessage": "{count} {count, plural, one {個の変換} other {個の変換}}を正常に削除しました。",
- "xpack.transform.transformList.bulkForceDeleteModalBody": "{count, plural, one {この} other {これらの}} {count} {count, plural, one {変換} other {変換}}を強制的に削除しますか?{count, plural, one {} other {}}現在の状態に関係なく、{count, plural, one {個の変換} other {個の変換}}は削除されます。",
"xpack.transform.transformList.bulkStartModalTitle": "{count} 件の{count, plural, one {変換} other {変換}}を開始",
"xpack.transform.transformList.completeBatchTransformBulkActionToolTip": "1 つまたは複数の変換が完了済みの一斉変換で、再度開始できません。",
"xpack.transform.transformList.completeBatchTransformToolTip": "{transformId} は完了済みの一斉変換で、再度開始できません。",
"xpack.transform.transformList.createTransformButton": "変換の作成",
"xpack.transform.transformList.deleteActionDisabledToolTipContent": "削除するにはデータフレームジョブを停止してください。",
"xpack.transform.transformList.deleteBulkActionDisabledToolTipContent": "削除するには、選択された変換のうちの 1 つまたは複数を停止する必要があります。",
- "xpack.transform.transformList.deleteModalBody": "この変換を削除してよろしいですか?",
"xpack.transform.transformList.deleteModalCancelButton": "キャンセル",
"xpack.transform.transformList.deleteModalDeleteButton": "削除",
"xpack.transform.transformList.deleteModalTitle": "{transformId} 削除",
@@ -18187,14 +18183,12 @@
"xpack.transform.transformList.editTransformGenericErrorMessage": "変換を削除するためのAPIエンドポイントの呼び出し中にエラーが発生しました。",
"xpack.transform.transformList.editTransformSuccessMessage": "変換{transformId}が更新されました。",
"xpack.transform.transformList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage": "ユーザーがディスティネーションインデックスを削除できるかどうかを確認するときにエラーが発生しました。",
- "xpack.transform.transformList.forceDeleteModalBody": "この変換を強制的に削除しますか?現在の状態に関係なく、変換が削除されます。",
"xpack.transform.transformList.refreshButtonLabel": "更新",
"xpack.transform.transformList.rowCollapse": "{transformId} の詳細を非表示",
"xpack.transform.transformList.rowExpand": "{transformId} の詳細を表示",
"xpack.transform.transformList.showDetailsColumn.screenReaderDescription": "このカラムには変換ごとの詳細を示すクリック可能なコントロールが含まれます",
"xpack.transform.transformList.startedTransformBulkToolTip": "1 つまたは複数の変換が既に開始済みです。",
"xpack.transform.transformList.startedTransformToolTip": "{transformId} は既に開始済みです。",
- "xpack.transform.transformList.startModalBody": "変換は、クラスターの検索とインデックスによる負荷を増やします。過剰な負荷が生じた場合は変換を停止してください。{count, plural, one {この} other {これら}} {count} 件の{count, plural, one {変換} other {変換}}を開始してよろしいですか?",
"xpack.transform.transformList.startModalCancelButton": "キャンセル",
"xpack.transform.transformList.startModalStartButton": "開始",
"xpack.transform.transformList.startModalTitle": "{transformId} を開始",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 9ffa81a921ba8..ffd7d0cfb0f87 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -1116,7 +1116,6 @@
"data.search.aggs.metrics.count.customLabel.help": "表示此聚合的定制标签",
"data.search.aggs.metrics.count.enabled.help": "指定是否启用此聚合",
"data.search.aggs.metrics.count.id.help": "此聚合的 ID",
- "data.search.aggs.metrics.count.json.help": "聚合发送至 Elasticsearch 时要包括的高级 json",
"data.search.aggs.metrics.count.schema.help": "要用于此聚合的方案",
"data.search.aggs.metrics.countLabel": "计数",
"data.search.aggs.metrics.countTitle": "计数",
@@ -4743,7 +4742,6 @@
"xpack.apm.customLink.empty": "未找到定制链接。设置自己的定制链接,如特定仪表板的链接或外部链接。",
"xpack.apm.emptyMessage.noDataFoundDescription": "尝试其他时间范围或重置搜索筛选。",
"xpack.apm.emptyMessage.noDataFoundLabel": "未找到任何数据",
- "xpack.apm.environment.allLabel": "全部",
"xpack.apm.error.prompt.body": "有关详情,请查看您的浏览器开发者控制台。",
"xpack.apm.error.prompt.title": "抱歉,发生错误 :(",
"xpack.apm.errorGroupDetails.avgLabel": "平均",
@@ -4781,6 +4779,7 @@
"xpack.apm.fetcher.error.title": "提取资源时出错",
"xpack.apm.fetcher.error.url": "URL",
"xpack.apm.filter.environment.label": "环境",
+ "xpack.apm.filter.environment.allLabel": "全部",
"xpack.apm.filter.environment.notDefinedLabel": "未定义",
"xpack.apm.filter.environment.selectEnvironmentLabel": "选择环境",
"xpack.apm.formatters.hoursTimeUnitLabel": "h",
@@ -18161,17 +18160,14 @@
"xpack.transform.transformForm.sizeNotationPlaceholder": "示例:{example1}、{example2}、{example3}、{example4}",
"xpack.transform.transformList.bulkDeleteDestIndexPatternSuccessMessage": "已成功删除 {count} 个目标索引{count, plural, one {模式} other {模式}}。",
"xpack.transform.transformList.bulkDeleteDestIndexSuccessMessage": "已成功删除 {count} 个目标{count, plural, one {索引} other {索引}}。",
- "xpack.transform.transformList.bulkDeleteModalBody": "确定要删除{count, plural, one {这} other {这}} {count} 个{count, plural, one {转换} other {转换}}?",
"xpack.transform.transformList.bulkDeleteModalTitle": "删除 {count} 个 {count, plural, one {转换} other {转换}}?",
"xpack.transform.transformList.bulkDeleteTransformSuccessMessage": "已成功删除 {count} 个{count, plural, one {转换} other {转换}}。",
- "xpack.transform.transformList.bulkForceDeleteModalBody": "确定要强制删除{count, plural, one {这} other {这}} {count} 个{count, plural, one {转换} other {转换}}?无论{count, plural, one {转换} other {转换}}的当前状态如何,都将删除{count, plural, one {} other {}}。",
"xpack.transform.transformList.bulkStartModalTitle": "启动 {count} 个 {count, plural, one {转换} other {转换}}?",
"xpack.transform.transformList.completeBatchTransformBulkActionToolTip": "一个或多个转换为已完成批量转换,无法重新启动。",
"xpack.transform.transformList.completeBatchTransformToolTip": "{transformId} 为已完成批量转换,无法重新启动。",
"xpack.transform.transformList.createTransformButton": "创建转换",
"xpack.transform.transformList.deleteActionDisabledToolTipContent": "停止数据帧作业,以便将其删除。",
"xpack.transform.transformList.deleteBulkActionDisabledToolTipContent": "一个或多个选定数据帧转换必须停止,才能删除。",
- "xpack.transform.transformList.deleteModalBody": "是否确定要删除此转换?",
"xpack.transform.transformList.deleteModalCancelButton": "取消",
"xpack.transform.transformList.deleteModalDeleteButton": "删除",
"xpack.transform.transformList.deleteModalTitle": "删除 {transformId}",
@@ -18194,14 +18190,12 @@
"xpack.transform.transformList.editTransformGenericErrorMessage": "调用用于更新转换的 API 终端时发生错误。",
"xpack.transform.transformList.editTransformSuccessMessage": "转换 {transformId} 已更新。",
"xpack.transform.transformList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage": "检查用户是否可以删除目标索引时发生错误",
- "xpack.transform.transformList.forceDeleteModalBody": "确定要强制删除此索引?无论转换的当前状态如何,都将删除。",
"xpack.transform.transformList.refreshButtonLabel": "刷新",
"xpack.transform.transformList.rowCollapse": "隐藏 {transformId} 的详情",
"xpack.transform.transformList.rowExpand": "显示 {transformId} 的详情",
"xpack.transform.transformList.showDetailsColumn.screenReaderDescription": "此列包含可单击控件,用于显示每个转换的更多详情",
"xpack.transform.transformList.startedTransformBulkToolTip": "一个或多个选定数据帧转换已启动。",
"xpack.transform.transformList.startedTransformToolTip": "{transformId} 已启动。",
- "xpack.transform.transformList.startModalBody": "转换将增加集群的搜索和索引负荷。如果负荷超载,请停止转换。是否确定要启动{count, plural, one {这} other {这}} {count} 个 {count, plural, one {转换} other {转换}}?",
"xpack.transform.transformList.startModalCancelButton": "取消",
"xpack.transform.transformList.startModalStartButton": "启动",
"xpack.transform.transformList.startModalTitle": "启动 {transformId}",
diff --git a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js
index a996910d4787a..10754d20118e9 100644
--- a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js
+++ b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js
@@ -13,31 +13,62 @@ export default function ({ getPageObjects, getService }) {
const filterBar = getService('filterBar');
describe('tooltip filter actions', () => {
- before(async () => {
+ async function loadDashboardAndOpenTooltip() {
await kibanaServer.uiSettings.replace({
defaultIndex: 'c698b940-e149-11e8-a35a-370a8516603a',
});
await PageObjects.common.navigateToApp('dashboard');
+ await PageObjects.dashboard.preserveCrossAppState();
await PageObjects.dashboard.loadSavedDashboard('dash for tooltip filter action test');
await PageObjects.maps.lockTooltipAtPosition(200, -200);
- });
+ }
+
+ describe('apply filter to current view', () => {
+ before(async () => {
+ await loadDashboardAndOpenTooltip();
+ });
+
+ it('should display create filter button when tooltip is locked', async () => {
+ const exists = await testSubjects.exists('mapTooltipCreateFilterButton');
+ expect(exists).to.be(true);
+ });
+
+ it('should create filters when create filter button is clicked', async () => {
+ await testSubjects.click('mapTooltipCreateFilterButton');
+ await testSubjects.click('applyFiltersPopoverButton');
+
+ // TODO: Fix me #64861
+ // const hasSourceFilter = await filterBar.hasFilter('name', 'charlie');
+ // expect(hasSourceFilter).to.be(true);
- it('should display create filter button when tooltip is locked', async () => {
- const exists = await testSubjects.exists('mapTooltipCreateFilterButton');
- expect(exists).to.be(true);
+ const hasJoinFilter = await filterBar.hasFilter('shape_name', 'charlie');
+ expect(hasJoinFilter).to.be(true);
+ });
});
- it('should create filters when create filter button is clicked', async () => {
- await testSubjects.click('mapTooltipCreateFilterButton');
- await testSubjects.click('applyFiltersPopoverButton');
+ describe('panel actions', () => {
+ before(async () => {
+ await loadDashboardAndOpenTooltip();
+ });
+
+ it('should display more actions button when tooltip is locked', async () => {
+ const exists = await testSubjects.exists('mapTooltipMoreActionsButton');
+ expect(exists).to.be(true);
+ });
+
+ it('should trigger drilldown action when clicked', async () => {
+ await testSubjects.click('mapTooltipMoreActionsButton');
+ await testSubjects.click('mapFilterActionButton__drilldown1');
- // TODO: Fix me #64861
- // const hasSourceFilter = await filterBar.hasFilter('name', 'charlie');
- // expect(hasSourceFilter).to.be(true);
+ // Assert on new dashboard with filter from action
+ await PageObjects.dashboard.waitForRenderComplete();
+ const panelCount = await PageObjects.dashboard.getPanelCount();
+ expect(panelCount).to.equal(2);
- const hasJoinFilter = await filterBar.hasFilter('shape_name', 'charlie');
- expect(hasJoinFilter).to.be(true);
+ const hasJoinFilter = await filterBar.hasFilter('shape_name', 'charlie');
+ expect(hasJoinFilter).to.be(true);
+ });
});
});
}
diff --git a/x-pack/test/functional/es_archives/maps/kibana/data.json b/x-pack/test/functional/es_archives/maps/kibana/data.json
index 198174bccb286..0f1fd3c09d706 100644
--- a/x-pack/test/functional/es_archives/maps/kibana/data.json
+++ b/x-pack/test/functional/es_archives/maps/kibana/data.json
@@ -1048,7 +1048,7 @@
"title" : "dash for tooltip filter action test",
"hits" : 0,
"description" : "Zoomed in so entire screen is covered by filter so click to open tooltip can not miss.",
- "panelsJSON" : "[{\"gridData\":{\"x\":0,\"y\":0,\"w\":48,\"h\":26,\"i\":\"1\"},\"version\":\"8.0.0\",\"panelIndex\":\"1\",\"embeddableConfig\":{\"mapCenter\":{\"lat\":-1.31919,\"lon\":59.53306,\"zoom\":9.67},\"isLayerTOCOpen\":false,\"openTOCDetails\":[\"n1t6f\"]},\"panelRefName\":\"panel_0\"}]",
+ "panelsJSON" : "[{\"version\":\"8.0.0\",\"gridData\":{\"x\":0,\"y\":0,\"w\":48,\"h\":26,\"i\":\"1\"},\"panelIndex\":\"1\",\"embeddableConfig\":{\"mapCenter\":{\"lat\":-1.31919,\"lon\":59.53306,\"zoom\":9.67},\"isLayerTOCOpen\":false,\"openTOCDetails\":[\"n1t6f\"],\"hiddenLayers\":[],\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"669a3521-1215-4228-9ced-77e2edf5ad17\",\"triggers\":[\"FILTER_TRIGGER\"],\"action\":{\"name\":\"drilldown1\",\"config\":{\"dashboardId\":\"19906970-2e40-11e9-85cb-6965aae20f13\",\"useCurrentFilters\":true,\"useCurrentDateRange\":true},\"factoryId\":\"DASHBOARD_TO_DASHBOARD_DRILLDOWN\"}}]}}},\"panelRefName\":\"panel_0\"}]",
"optionsJSON" : "{\"useMargins\":true,\"hidePanelTitles\":false}",
"version" : 1,
"timeRestore" : true,
@@ -1071,9 +1071,9 @@
}
],
"migrationVersion" : {
- "dashboard" : "7.0.0"
+ "dashboard" : "7.3.0"
},
- "updated_at" : "2019-06-14T14:09:25.039Z"
+ "updated_at" : "2020-08-26T14:32:27.854Z"
}
}
}
diff --git a/x-pack/test/licensing_plugin/public/feature_usage.ts b/x-pack/test/licensing_plugin/public/feature_usage.ts
new file mode 100644
index 0000000000000..15d302d71bfab
--- /dev/null
+++ b/x-pack/test/licensing_plugin/public/feature_usage.ts
@@ -0,0 +1,82 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import expect from '@kbn/expect';
+import { FtrProviderContext } from '../services';
+import {
+ LicensingPluginSetup,
+ LicensingPluginStart,
+ LicenseType,
+} from '../../../plugins/licensing/public';
+import '../../../../test/plugin_functional/plugins/core_provider_plugin/types';
+
+interface FeatureUsage {
+ last_used?: number;
+ license_level: LicenseType;
+ name: string;
+}
+
+// eslint-disable-next-line import/no-default-export
+export default function (ftrContext: FtrProviderContext) {
+ const { getService, getPageObjects } = ftrContext;
+ const supertest = getService('supertest');
+ const browser = getService('browser');
+ const PageObjects = getPageObjects(['common', 'security']);
+
+ const registerFeature = async (featureName: string, licenseType: LicenseType) => {
+ await browser.executeAsync(
+ async (feature, type, cb) => {
+ const { setup } = window._coreProvider;
+ const licensing: LicensingPluginSetup = setup.plugins.licensing;
+ await licensing.featureUsage.register(feature, type);
+ cb();
+ },
+ featureName,
+ licenseType
+ );
+ };
+
+ const notifyFeatureUsage = async (featureName: string, lastUsed: number) => {
+ await browser.executeAsync(
+ async (feature, time, cb) => {
+ const { start } = window._coreProvider;
+ const licensing: LicensingPluginStart = start.plugins.licensing;
+ await licensing.featureUsage.notifyUsage(feature, time);
+ cb();
+ },
+ featureName,
+ lastUsed
+ );
+ };
+
+ describe('feature_usage API', () => {
+ before(async () => {
+ await PageObjects.security.login();
+ });
+
+ it('allows to register features to the server', async () => {
+ await registerFeature('test-client-A', 'gold');
+ await registerFeature('test-client-B', 'enterprise');
+
+ const response = await supertest.get('/api/licensing/feature_usage').expect(200);
+ const features = response.body.features.map(({ name }: FeatureUsage) => name);
+
+ expect(features).to.contain('test-client-A');
+ expect(features).to.contain('test-client-B');
+ });
+
+ it('allows to notify feature usage', async () => {
+ const now = new Date();
+
+ await notifyFeatureUsage('test-client-A', now.getTime());
+
+ const response = await supertest.get('/api/licensing/feature_usage').expect(200);
+ const features = response.body.features as FeatureUsage[];
+
+ expect(features.find((f) => f.name === 'test-client-A')?.last_used).to.be(now.toISOString());
+ expect(features.find((f) => f.name === 'test-client-B')?.last_used).to.be(null);
+ });
+ });
+}
diff --git a/x-pack/test/licensing_plugin/public/index.ts b/x-pack/test/licensing_plugin/public/index.ts
index 86a3c21cfdb39..268a74c56bd72 100644
--- a/x-pack/test/licensing_plugin/public/index.ts
+++ b/x-pack/test/licensing_plugin/public/index.ts
@@ -10,6 +10,7 @@ import { FtrProviderContext } from '../services';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Licensing plugin public client', function () {
this.tags('ciGroup2');
+ loadTestFile(require.resolve('./feature_usage'));
// MUST BE LAST! CHANGES LICENSE TYPE!
loadTestFile(require.resolve('./updates'));
});
diff --git a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts
index d9cb671282124..dbdaf494fdf27 100644
--- a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts
+++ b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts
@@ -30,7 +30,8 @@ export default function ({ getService }: FtrProviderContext) {
return (await es.search({ index: '.kibana_security_session*' })).hits.total.value;
}
- describe('Session Lifespan cleanup', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/76223
+ describe.skip('Session Lifespan cleanup', () => {
beforeEach(async () => {
await es.cluster.health({ index: '.kibana_security_session*', waitForStatus: 'green' });
await es.deleteByQuery({