groups: [
{
groupId: 'columns',
- groupLabel: i18n.translate('xpack.lens.datatable.columns', {
- defaultMessage: 'Columns',
+ groupLabel: i18n.translate('xpack.lens.datatable.breakdown', {
+ defaultMessage: 'Break down by',
}),
layerId: state.layers[0].layerId,
- accessors: sortedColumns,
+ accessors: sortedColumns.filter((c) => datasource.getOperationForColumnId(c)?.isBucketed),
supportsMoreColumns: true,
- filterOperations: () => true,
+ filterOperations: (op) => op.isBucketed,
dataTestSubj: 'lnsDatatable_column',
},
+ {
+ groupId: 'metrics',
+ groupLabel: i18n.translate('xpack.lens.datatable.metrics', {
+ defaultMessage: 'Metrics',
+ }),
+ layerId: state.layers[0].layerId,
+ accessors: sortedColumns.filter(
+ (c) => !datasource.getOperationForColumnId(c)?.isBucketed
+ ),
+ supportsMoreColumns: true,
+ filterOperations: (op) => !op.isBucketed,
+ required: true,
+ dataTestSubj: 'lnsDatatable_metrics',
+ },
],
};
},
diff --git a/x-pack/plugins/lens/public/drag_drop/__snapshots__/drag_drop.test.tsx.snap b/x-pack/plugins/lens/public/drag_drop/__snapshots__/drag_drop.test.tsx.snap
index d18a2db614f55..3581151dd5f76 100644
--- a/x-pack/plugins/lens/public/drag_drop/__snapshots__/drag_drop.test.tsx.snap
+++ b/x-pack/plugins/lens/public/drag_drop/__snapshots__/drag_drop.test.tsx.snap
@@ -9,6 +9,20 @@ exports[`DragDrop droppable is reflected in the className 1`] = `
`;
+exports[`DragDrop items that have droppable=false get special styling when another item is dragged 1`] = `
+
+ Hello!
+
+`;
+
exports[`DragDrop renders if nothing is being dragged 1`] = `
{
const value = {};
const component = mount(
-
+
Hello!
@@ -127,4 +127,63 @@ describe('DragDrop', () => {
expect(component).toMatchSnapshot();
});
+
+ test('items that have droppable=false get special styling when another item is dragged', () => {
+ const component = mount(
+ {}}>
+
+ Ignored
+
+ {}} droppable={false}>
+ Hello!
+
+
+ );
+
+ expect(component.find('[data-test-subj="lnsDragDrop"]').at(1)).toMatchSnapshot();
+ });
+
+ test('additional styles are reflected in the className until drop', () => {
+ let dragging: string | undefined;
+ const getAdditionalClasses = jest.fn().mockReturnValue('additional');
+ const component = mount(
+ {
+ dragging = 'hello';
+ }}
+ >
+
+ Ignored
+
+ {}}
+ droppable
+ getAdditionalClassesOnEnter={getAdditionalClasses}
+ >
+ Hello!
+
+
+ );
+
+ const dataTransfer = {
+ setData: jest.fn(),
+ getData: jest.fn(),
+ };
+ component
+ .find('[data-test-subj="lnsDragDrop"]')
+ .first()
+ .simulate('dragstart', { dataTransfer });
+ jest.runAllTimers();
+
+ component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('dragover');
+ expect(component.find('.additional')).toHaveLength(1);
+
+ component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('dragleave');
+ expect(component.find('.additional')).toHaveLength(0);
+
+ component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('dragover');
+ component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('drop');
+ expect(component.find('.additional')).toHaveLength(0);
+ });
});
diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx
index 5a0fc3b3839f7..85bdd24bd4f80 100644
--- a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx
+++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx
@@ -49,6 +49,11 @@ interface BaseProps {
*/
droppable?: boolean;
+ /**
+ * Additional class names to apply when another element is over the drop target
+ */
+ getAdditionalClassesOnEnter?: () => string;
+
/**
* The optional test subject associated with this DOM element.
*/
@@ -97,6 +102,12 @@ export const DragDrop = (props: Props) => {
{...props}
dragging={droppable ? dragging : undefined}
isDragging={!!(draggable && value === dragging)}
+ isNotDroppable={
+ // If the configuration has provided a droppable flag, but this particular item is not
+ // droppable, then it should be less prominent. Ignores items that are both
+ // draggable and drop targets
+ droppable === false && Boolean(dragging) && value !== dragging
+ }
setDragging={setDragging}
/>
);
@@ -107,9 +118,13 @@ const DragDropInner = React.memo(function DragDropInner(
dragging: unknown;
setDragging: (dragging: unknown) => void;
isDragging: boolean;
+ isNotDroppable: boolean;
}
) {
- const [state, setState] = useState({ isActive: false });
+ const [state, setState] = useState({
+ isActive: false,
+ dragEnterClassNames: '',
+ });
const {
className,
onDrop,
@@ -120,13 +135,20 @@ const DragDropInner = React.memo(function DragDropInner(
dragging,
setDragging,
isDragging,
+ isNotDroppable,
} = props;
- const classes = classNames('lnsDragDrop', className, {
- 'lnsDragDrop-isDropTarget': droppable,
- 'lnsDragDrop-isActiveDropTarget': droppable && state.isActive,
- 'lnsDragDrop-isDragging': isDragging,
- });
+ const classes = classNames(
+ 'lnsDragDrop',
+ className,
+ {
+ 'lnsDragDrop-isDropTarget': droppable,
+ 'lnsDragDrop-isActiveDropTarget': droppable && state.isActive,
+ 'lnsDragDrop-isDragging': isDragging,
+ 'lnsDragDrop-isNotDroppable': isNotDroppable,
+ },
+ state.dragEnterClassNames
+ );
const dragStart = (e: DroppableEvent) => {
// Setting stopPropgagation causes Chrome failures, so
@@ -159,19 +181,25 @@ const DragDropInner = React.memo(function DragDropInner(
// An optimization to prevent a bunch of React churn.
if (!state.isActive) {
- setState({ ...state, isActive: true });
+ setState({
+ ...state,
+ isActive: true,
+ dragEnterClassNames: props.getAdditionalClassesOnEnter
+ ? props.getAdditionalClassesOnEnter()
+ : '',
+ });
}
};
const dragLeave = () => {
- setState({ ...state, isActive: false });
+ setState({ ...state, isActive: false, dragEnterClassNames: '' });
};
const drop = (e: DroppableEvent) => {
e.preventDefault();
e.stopPropagation();
- setState({ ...state, isActive: false });
+ setState({ ...state, isActive: false, dragEnterClassNames: '' });
setDragging(undefined);
if (onDrop && droppable) {
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss
index 4e13fd95d1961..62bc6d7ed7cc8 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss
@@ -27,6 +27,14 @@
overflow: hidden;
}
+.lnsLayerPanel__dimension-isHidden {
+ opacity: 0;
+}
+
+.lnsLayerPanel__dimension-isReplacing {
+ text-decoration: line-through;
+}
+
.lnsLayerPanel__triggerLink {
padding: $euiSizeS;
width: 100%;
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx
index b3ad03b71770c..85dbee6de524f 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx
@@ -12,6 +12,7 @@ import {
createMockDatasource,
DatasourceMock,
} from '../../mocks';
+import { ChildDragDropProvider } from '../../../drag_drop';
import { EuiFormRow, EuiPopover } from '@elastic/eui';
import { mount } from 'enzyme';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
@@ -272,6 +273,7 @@ describe('LayerPanel', () => {
expect(component.find(EuiPopover).prop('isOpen')).toBe(true);
});
+
it('should close the popover when the active visualization changes', () => {
/**
* The ID generation system for new dimensions has been messy before, so
@@ -324,4 +326,151 @@ describe('LayerPanel', () => {
expect(component.find(EuiPopover).prop('isOpen')).toBe(false);
});
});
+
+ // This test is more like an integration test, since the layer panel owns all
+ // the coordination between drag and drop
+ describe('drag and drop behavior', () => {
+ it('should determine if the datasource supports dropping of a field onto empty dimension', () => {
+ mockVisualization.getConfiguration.mockReturnValue({
+ groups: [
+ {
+ groupLabel: 'A',
+ groupId: 'a',
+ accessors: [],
+ filterOperations: () => true,
+ supportsMoreColumns: true,
+ dataTestSubj: 'lnsGroup',
+ },
+ ],
+ });
+
+ mockDatasource.canHandleDrop.mockReturnValue(true);
+
+ const draggingField = { field: { name: 'dragged' }, indexPatternId: 'a' };
+
+ const component = mountWithIntl(
+
+
+
+ );
+
+ expect(mockDatasource.canHandleDrop).toHaveBeenCalledWith(
+ expect.objectContaining({
+ dragDropContext: expect.objectContaining({
+ dragging: draggingField,
+ }),
+ })
+ );
+
+ component.find('DragDrop[data-test-subj="lnsGroup"]').first().simulate('drop');
+
+ expect(mockDatasource.onDrop).toHaveBeenCalledWith(
+ expect.objectContaining({
+ dragDropContext: expect.objectContaining({
+ dragging: draggingField,
+ }),
+ })
+ );
+ });
+
+ it('should allow drag to move between groups', () => {
+ (generateId as jest.Mock).mockReturnValue(`newid`);
+
+ mockVisualization.getConfiguration.mockReturnValue({
+ groups: [
+ {
+ groupLabel: 'A',
+ groupId: 'a',
+ accessors: ['a'],
+ filterOperations: () => true,
+ supportsMoreColumns: false,
+ dataTestSubj: 'lnsGroupA',
+ },
+ {
+ groupLabel: 'B',
+ groupId: 'b',
+ accessors: ['b'],
+ filterOperations: () => true,
+ supportsMoreColumns: true,
+ dataTestSubj: 'lnsGroupB',
+ },
+ ],
+ });
+
+ mockDatasource.canHandleDrop.mockReturnValue(true);
+
+ const draggingOperation = { layerId: 'first', columnId: 'a', groupId: 'a' };
+
+ const component = mountWithIntl(
+
+
+
+ );
+
+ expect(mockDatasource.canHandleDrop).toHaveBeenCalledTimes(2);
+ expect(mockDatasource.canHandleDrop).toHaveBeenCalledWith(
+ expect.objectContaining({
+ dragDropContext: expect.objectContaining({
+ dragging: draggingOperation,
+ }),
+ })
+ );
+
+ // Simulate drop on the pre-populated dimension
+ component.find('DragDrop[data-test-subj="lnsGroupB"]').at(0).simulate('drop');
+ expect(mockDatasource.onDrop).toHaveBeenCalledWith(
+ expect.objectContaining({
+ columnId: 'b',
+ dragDropContext: expect.objectContaining({
+ dragging: draggingOperation,
+ }),
+ })
+ );
+
+ // Simulate drop on the empty dimension
+ component.find('DragDrop[data-test-subj="lnsGroupB"]').at(1).simulate('drop');
+ expect(mockDatasource.onDrop).toHaveBeenCalledWith(
+ expect.objectContaining({
+ columnId: 'newid',
+ dragDropContext: expect.objectContaining({
+ dragging: draggingOperation,
+ }),
+ })
+ );
+ });
+
+ it('should prevent dropping in the same group', () => {
+ mockVisualization.getConfiguration.mockReturnValue({
+ groups: [
+ {
+ groupLabel: 'A',
+ groupId: 'a',
+ accessors: ['a', 'b'],
+ filterOperations: () => true,
+ supportsMoreColumns: true,
+ dataTestSubj: 'lnsGroup',
+ },
+ ],
+ });
+
+ const draggingOperation = { layerId: 'first', columnId: 'a', groupId: 'a' };
+
+ const component = mountWithIntl(
+
+
+
+ );
+
+ expect(mockDatasource.canHandleDrop).not.toHaveBeenCalled();
+
+ component.find('DragDrop[data-test-subj="lnsGroup"]').at(0).simulate('drop');
+ expect(mockDatasource.onDrop).not.toHaveBeenCalled();
+
+ component.find('DragDrop[data-test-subj="lnsGroup"]').at(1).simulate('drop');
+ expect(mockDatasource.onDrop).not.toHaveBeenCalled();
+
+ component.find('DragDrop[data-test-subj="lnsGroup"]').at(2).simulate('drop');
+ expect(mockDatasource.onDrop).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx
index b2804cfddba58..b45dd13bfa4fd 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx
@@ -17,8 +17,9 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import classNames from 'classnames';
import { NativeRenderer } from '../../../native_renderer';
-import { StateSetter } from '../../../types';
+import { StateSetter, isDraggedOperation } from '../../../types';
import { DragContext, DragDrop, ChildDragDropProvider } from '../../../drag_drop';
import { LayerSettings } from './layer_settings';
import { trackUiEvent } from '../../../lens_ui_telemetry';
@@ -154,6 +155,7 @@ export function LayerPanel(
{groups.map((group, index) => {
const newId = generateId();
const isMissing = !isEmptyLayer && group.required && group.accessors.length === 0;
+
return (
{
+ // If we are dragging another column, add an indication that the behavior will be a replacement'
+ if (
+ isDraggedOperation(dragDropContext.dragging) &&
+ group.groupId !== dragDropContext.dragging.groupId
+ ) {
+ return 'lnsLayerPanel__dimension-isReplacing';
+ }
+ return '';
+ }}
data-test-subj={group.dataTestSubj}
+ draggable={true}
+ value={{ columnId: accessor, groupId: group.groupId, layerId }}
+ label={group.groupLabel}
droppable={
- dragDropContext.dragging &&
+ Boolean(dragDropContext.dragging) &&
+ // Verify that the dragged item is not coming from the same group
+ // since this would be a reorder
+ (!isDraggedOperation(dragDropContext.dragging) ||
+ dragDropContext.dragging.groupId !== group.groupId) &&
layerDatasource.canHandleDrop({
...layerDatasourceDropProps,
columnId: accessor,
@@ -226,12 +250,22 @@ export function LayerPanel(
})
}
onDrop={(droppedItem) => {
- layerDatasource.onDrop({
+ const dropResult = layerDatasource.onDrop({
...layerDatasourceDropProps,
droppedItem,
columnId: accessor,
filterOperations: group.filterOperations,
});
+ if (typeof dropResult === 'object') {
+ // When a column is moved, we delete the reference to the old
+ props.updateVisualization(
+ activeVisualization.removeDimension({
+ layerId,
+ columnId: dropResult.deleted,
+ prevState: props.visualizationState,
+ })
+ );
+ }
}}
>
{
- const dropSuccess = layerDatasource.onDrop({
+ const dropResult = layerDatasource.onDrop({
...layerDatasourceDropProps,
droppedItem,
columnId: newId,
filterOperations: group.filterOperations,
});
- if (dropSuccess) {
+ if (dropResult) {
props.updateVisualization(
activeVisualization.setDimension({
layerId,
@@ -338,6 +376,17 @@ export function LayerPanel(
prevState: props.visualizationState,
})
);
+
+ if (typeof dropResult === 'object') {
+ // When a column is moved, we delete the reference to the old
+ props.updateVisualization(
+ activeVisualization.removeDimension({
+ layerId,
+ columnId: dropResult.deleted,
+ prevState: props.visualizationState,
+ })
+ );
+ }
}
}}
>
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx
index 3ee109376d975..f184d5628ab1c 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx
@@ -1378,6 +1378,66 @@ describe('IndexPatternDimensionEditorPanel', () => {
).toBe(false);
});
+ it('is droppable if the dragged column is compatible', () => {
+ expect(
+ canHandleDrop({
+ ...defaultProps,
+ dragDropContext: {
+ ...dragDropContext,
+ dragging: {
+ columnId: 'col1',
+ groupId: 'a',
+ layerId: 'myLayer',
+ },
+ },
+ state: dragDropState(),
+ columnId: 'col2',
+ filterOperations: (op: OperationMetadata) => true,
+ layerId: 'myLayer',
+ })
+ ).toBe(true);
+ });
+
+ it('is not droppable if the dragged column is the same as the current column', () => {
+ expect(
+ canHandleDrop({
+ ...defaultProps,
+ dragDropContext: {
+ ...dragDropContext,
+ dragging: {
+ columnId: 'col1',
+ groupId: 'a',
+ layerId: 'myLayer',
+ },
+ },
+ state: dragDropState(),
+ columnId: 'col1',
+ filterOperations: (op: OperationMetadata) => true,
+ layerId: 'myLayer',
+ })
+ ).toBe(false);
+ });
+
+ it('is not droppable if the dragged column is incompatible', () => {
+ expect(
+ canHandleDrop({
+ ...defaultProps,
+ dragDropContext: {
+ ...dragDropContext,
+ dragging: {
+ columnId: 'col1',
+ groupId: 'a',
+ layerId: 'myLayer',
+ },
+ },
+ state: dragDropState(),
+ columnId: 'col2',
+ filterOperations: (op: OperationMetadata) => op.dataType === 'number',
+ layerId: 'myLayer',
+ })
+ ).toBe(false);
+ });
+
it('appends the dropped column when a field is dropped', () => {
const dragging = {
field: { type: 'number', name: 'bar', aggregatable: true },
@@ -1526,5 +1586,109 @@ describe('IndexPatternDimensionEditorPanel', () => {
},
});
});
+
+ it('updates the column id when moving an operation to an empty dimension', () => {
+ const dragging = {
+ columnId: 'col1',
+ groupId: 'a',
+ layerId: 'myLayer',
+ };
+ const testState = dragDropState();
+
+ onDrop({
+ ...defaultProps,
+ dragDropContext: {
+ ...dragDropContext,
+ dragging,
+ },
+ droppedItem: dragging,
+ state: testState,
+ columnId: 'col2',
+ filterOperations: (op: OperationMetadata) => true,
+ layerId: 'myLayer',
+ });
+
+ expect(setState).toBeCalledTimes(1);
+ expect(setState).toHaveBeenCalledWith({
+ ...testState,
+ layers: {
+ myLayer: {
+ ...testState.layers.myLayer,
+ columnOrder: ['col2'],
+ columns: {
+ col2: testState.layers.myLayer.columns.col1,
+ },
+ },
+ },
+ });
+ });
+
+ it('replaces an operation when moving to a populated dimension', () => {
+ const dragging = {
+ columnId: 'col2',
+ groupId: 'a',
+ layerId: 'myLayer',
+ };
+ const testState = dragDropState();
+ testState.layers.myLayer = {
+ indexPatternId: 'foo',
+ columnOrder: ['col1', 'col2', 'col3'],
+ columns: {
+ col1: testState.layers.myLayer.columns.col1,
+
+ col2: {
+ label: 'Top values of src',
+ dataType: 'string',
+ isBucketed: true,
+
+ // Private
+ operationType: 'terms',
+ params: {
+ orderBy: { type: 'column', columnId: 'col3' },
+ orderDirection: 'desc',
+ size: 10,
+ },
+ sourceField: 'src',
+ },
+ col3: {
+ label: 'Count',
+ dataType: 'number',
+ isBucketed: false,
+
+ // Private
+ operationType: 'count',
+ sourceField: 'Records',
+ },
+ },
+ };
+
+ onDrop({
+ ...defaultProps,
+ dragDropContext: {
+ ...dragDropContext,
+ dragging,
+ },
+ droppedItem: dragging,
+ state: testState,
+ columnId: 'col1',
+ filterOperations: (op: OperationMetadata) => true,
+ layerId: 'myLayer',
+ });
+
+ expect(setState).toBeCalledTimes(1);
+ expect(setState).toHaveBeenCalledWith({
+ ...testState,
+ layers: {
+ myLayer: {
+ ...testState.layers.myLayer,
+ columnOrder: ['col1', 'col3'],
+ columns: {
+ col1: testState.layers.myLayer.columns.col2,
+ col3: testState.layers.myLayer.columns.col3,
+ },
+ },
+ },
+ });
+ });
});
});
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx
index 1e8f73b19a3b0..1fbbefd8f1117 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx
@@ -15,6 +15,7 @@ import {
DatasourceDimensionEditorProps,
DatasourceDimensionDropProps,
DatasourceDimensionDropHandlerProps,
+ isDraggedOperation,
} from '../../types';
import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public';
import { IndexPatternColumn, OperationType } from '../indexpattern';
@@ -99,16 +100,25 @@ export function canHandleDrop(props: DatasourceDimensionDropProps
-): boolean {
+export function onDrop(props: DatasourceDimensionDropHandlerProps) {
const operationFieldSupportMatrix = getOperationFieldSupportMatrix(props);
const droppedItem = props.droppedItem;
@@ -116,6 +126,42 @@ export function onDrop(
return Boolean(operationFieldSupportMatrix.operationByField[field.name]);
}
+ if (isDraggedOperation(droppedItem) && droppedItem.layerId === props.layerId) {
+ const layer = props.state.layers[props.layerId];
+ const op = { ...layer.columns[droppedItem.columnId] };
+ if (!props.filterOperations(op)) {
+ return false;
+ }
+
+ const newColumns = { ...layer.columns };
+ delete newColumns[droppedItem.columnId];
+ newColumns[props.columnId] = op;
+
+ const newColumnOrder = [...layer.columnOrder];
+ const oldIndex = newColumnOrder.findIndex((c) => c === droppedItem.columnId);
+ const newIndex = newColumnOrder.findIndex((c) => c === props.columnId);
+
+ if (newIndex === -1) {
+ newColumnOrder[oldIndex] = props.columnId;
+ } else {
+ newColumnOrder.splice(oldIndex, 1);
+ }
+
+ // Time to replace
+ props.setState({
+ ...props.state,
+ layers: {
+ ...props.state.layers,
+ [props.layerId]: {
+ ...layer,
+ columnOrder: newColumnOrder,
+ columns: newColumns,
+ },
+ },
+ });
+ return { deleted: droppedItem.columnId };
+ }
+
if (!isDraggedField(droppedItem) || !hasOperationForField(droppedItem.field)) {
// TODO: What do we do if we couldn't find a column?
return false;
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts
index 0cd92fd96c952..374dbe77b4ca3 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts
@@ -4,12 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { DataType } from '../types';
import { DraggedField } from './indexpattern';
import {
BaseIndexPatternColumn,
FieldBasedIndexPatternColumn,
} from './operations/definitions/column_types';
-import { DataType } from '../types';
/**
* Normalizes the specified operation type. (e.g. document operations
diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts
index 729daed7223fe..d8b77afdfe004 100644
--- a/x-pack/plugins/lens/public/types.ts
+++ b/x-pack/plugins/lens/public/types.ts
@@ -157,7 +157,7 @@ export interface Datasource {
renderDimensionEditor: (domElement: Element, props: DatasourceDimensionEditorProps) => void;
renderLayerPanel: (domElement: Element, props: DatasourceLayerPanelProps) => void;
canHandleDrop: (props: DatasourceDimensionDropProps) => boolean;
- onDrop: (props: DatasourceDimensionDropHandlerProps) => boolean;
+ onDrop: (props: DatasourceDimensionDropHandlerProps) => false | true | { deleted: string };
toExpression: (state: T, layerId: string) => Ast | string | null;
@@ -230,6 +230,22 @@ export interface DatasourceLayerPanelProps {
setState: StateSetter;
}
+export interface DraggedOperation {
+ layerId: string;
+ groupId: string;
+ columnId: string;
+}
+
+export function isDraggedOperation(
+ operationCandidate: unknown
+): operationCandidate is DraggedOperation {
+ return (
+ typeof operationCandidate === 'object' &&
+ operationCandidate !== null &&
+ 'columnId' in operationCandidate
+ );
+}
+
export type DatasourceDimensionDropProps = SharedDimensionProps & {
layerId: string;
columnId: string;
diff --git a/x-pack/plugins/lists/public/exceptions/api.test.ts b/x-pack/plugins/lists/public/exceptions/api.test.ts
index b02a82f98af91..0c31015fc9f5e 100644
--- a/x-pack/plugins/lists/public/exceptions/api.test.ts
+++ b/x-pack/plugins/lists/public/exceptions/api.test.ts
@@ -375,7 +375,7 @@ describe('Exceptions Lists API', () => {
namespace_type: 'single,single',
page: '1',
per_page: '20',
- sort_field: 'created_at',
+ sort_field: 'exception-list.created_at',
sort_order: 'desc',
},
signal: abortCtrl.signal,
@@ -408,7 +408,7 @@ describe('Exceptions Lists API', () => {
namespace_type: 'single',
page: '1',
per_page: '20',
- sort_field: 'created_at',
+ sort_field: 'exception-list.created_at',
sort_order: 'desc',
},
signal: abortCtrl.signal,
@@ -441,7 +441,7 @@ describe('Exceptions Lists API', () => {
namespace_type: 'agnostic',
page: '1',
per_page: '20',
- sort_field: 'created_at',
+ sort_field: 'exception-list.created_at',
sort_order: 'desc',
},
signal: abortCtrl.signal,
@@ -474,7 +474,7 @@ describe('Exceptions Lists API', () => {
namespace_type: 'agnostic',
page: '1',
per_page: '20',
- sort_field: 'created_at',
+ sort_field: 'exception-list.created_at',
sort_order: 'desc',
},
signal: abortCtrl.signal,
@@ -508,7 +508,7 @@ describe('Exceptions Lists API', () => {
namespace_type: 'agnostic',
page: '1',
per_page: '20',
- sort_field: 'created_at',
+ sort_field: 'exception-list.created_at',
sort_order: 'desc',
},
signal: abortCtrl.signal,
diff --git a/x-pack/plugins/lists/public/exceptions/api.ts b/x-pack/plugins/lists/public/exceptions/api.ts
index 3f5ec80320503..824a25296260f 100644
--- a/x-pack/plugins/lists/public/exceptions/api.ts
+++ b/x-pack/plugins/lists/public/exceptions/api.ts
@@ -288,7 +288,7 @@ export const fetchExceptionListsItemsByListIds = async ({
namespace_type: namespaceTypes.join(','),
page: pagination.page ? `${pagination.page}` : '1',
per_page: pagination.perPage ? `${pagination.perPage}` : '20',
- sort_field: 'created_at',
+ sort_field: 'exception-list.created_at',
sort_order: 'desc',
...(filters.trim() !== '' ? { filter: filters } : {}),
};
diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js
index 694e50cfe3e36..6cb1f87648da1 100644
--- a/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js
+++ b/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js
@@ -125,11 +125,11 @@ class PipelineEditorUi extends React.Component {
onPipelineSave = () => {
const { pipelineService, toastNotifications, intl } = this.props;
- const { id } = this.state.pipeline;
+ const { id, ...pipelineToStore } = this.state.pipeline;
return pipelineService
.savePipeline({
id,
- upstreamJSON: this.state.pipeline,
+ upstreamJSON: pipelineToStore,
})
.then(() => {
toastNotifications.addSuccess(
diff --git a/x-pack/plugins/logstash/server/models/pipeline/pipeline.ts b/x-pack/plugins/logstash/server/models/pipeline/pipeline.ts
index 8ce04c83afdbf..0b7c3888b6f03 100755
--- a/x-pack/plugins/logstash/server/models/pipeline/pipeline.ts
+++ b/x-pack/plugins/logstash/server/models/pipeline/pipeline.ts
@@ -11,14 +11,14 @@ import { i18n } from '@kbn/i18n';
interface PipelineOptions {
id: string;
- description: string;
+ description?: string;
pipeline: string;
username?: string;
settings?: Record;
}
interface DownstreamPipeline {
- description: string;
+ description?: string;
pipeline: string;
settings?: Record;
}
@@ -27,7 +27,7 @@ interface DownstreamPipeline {
*/
export class Pipeline {
public readonly id: string;
- public readonly description: string;
+ public readonly description?: string;
public readonly username?: string;
public readonly pipeline: string;
private readonly settings: Record;
diff --git a/x-pack/plugins/logstash/server/routes/pipeline/save.ts b/x-pack/plugins/logstash/server/routes/pipeline/save.ts
index e484d0e221b6d..755a82e670a2a 100644
--- a/x-pack/plugins/logstash/server/routes/pipeline/save.ts
+++ b/x-pack/plugins/logstash/server/routes/pipeline/save.ts
@@ -22,8 +22,7 @@ export function registerPipelineSaveRoute(router: IRouter, security?: SecurityPl
id: schema.string(),
}),
body: schema.object({
- id: schema.string(),
- description: schema.string(),
+ description: schema.maybe(schema.string()),
pipeline: schema.string(),
settings: schema.maybe(schema.object({}, { unknowns: 'allow' })),
}),
diff --git a/x-pack/plugins/ml/common/constants/settings.ts b/x-pack/plugins/ml/common/constants/settings.ts
index 2df2ecd22e078..bab2aa2f2a0ae 100644
--- a/x-pack/plugins/ml/common/constants/settings.ts
+++ b/x-pack/plugins/ml/common/constants/settings.ts
@@ -5,3 +5,11 @@
*/
export const FILE_DATA_VISUALIZER_MAX_FILE_SIZE = 'ml:fileDataVisualizerMaxFileSize';
+export const ANOMALY_DETECTION_ENABLE_TIME_RANGE = 'ml:anomalyDetection:results:enableTimeDefaults';
+export const ANOMALY_DETECTION_DEFAULT_TIME_RANGE = 'ml:anomalyDetection:results:timeDefaults';
+
+export const DEFAULT_AD_RESULTS_TIME_FILTER = {
+ from: 'now-15m',
+ to: 'now',
+};
+export const DEFAULT_ENABLE_AD_RESULTS_TIME_FILTER = false;
diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js
index 5a7d2a9c3ddaa..fdeab0c49e32b 100644
--- a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js
+++ b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js
@@ -390,6 +390,7 @@ class LinksMenuUI extends Component {
defaultMessage: 'Select action for anomaly at {time}',
values: { time: formatHumanReadableDateTimeSeconds(anomaly.time) },
})}
+ data-test-subj="mlAnomaliesListRowActionsButton"
/>
);
@@ -404,6 +405,7 @@ class LinksMenuUI extends Component {
this.closePopover();
this.openCustomUrl(customUrl);
}}
+ data-test-subj={`mlAnomaliesListRowActionCustomUrlButton_${index}`}
>
{customUrl.url_name}
@@ -420,6 +422,7 @@ class LinksMenuUI extends Component {
this.closePopover();
this.viewSeries();
}}
+ data-test-subj="mlAnomaliesListRowActionViewSeriesButton"
>