Skip to content

Commit 604c6ed

Browse files
committed
[lens] Index pattern suggest on drop
1 parent 9ea8b9a commit 604c6ed

7 files changed

Lines changed: 428 additions & 19 deletions

File tree

x-pack/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { ReactWrapper } from 'enzyme';
2121
const waitForPromises = () => new Promise(resolve => setTimeout(resolve));
2222

2323
describe('workspace_panel', () => {
24-
let mockVisualization: Visualization;
24+
let mockVisualization: jest.Mocked<Visualization>;
2525
let mockDatasource: DatasourceMock;
2626

2727
let expressionRendererMock: jest.Mock<React.ReactElement, [ExpressionRendererProps]>;
@@ -274,4 +274,136 @@ Object {
274274
expect(instance.find(expressionRendererMock).length).toBe(1);
275275
});
276276
});
277+
278+
describe('suggestions from dropping in workspace panel', () => {
279+
let mockDispatch: jest.Mock;
280+
281+
beforeEach(() => {
282+
mockDispatch = jest.fn();
283+
instance = mount(
284+
<WorkspacePanel
285+
activeDatasource={mockDatasource}
286+
datasourceState={{}}
287+
activeVisualizationId={null}
288+
visualizationMap={{
289+
vis: mockVisualization,
290+
}}
291+
visualizationState={{}}
292+
datasourcePublicAPI={mockDatasource.publicAPIMock}
293+
dispatch={mockDispatch}
294+
ExpressionRenderer={expressionRendererMock}
295+
/>
296+
);
297+
});
298+
299+
it('should immediately transition if exactly one suggestion is returned', () => {
300+
const expectedTable = {
301+
datasourceSuggestionId: 0,
302+
isMultiRow: true,
303+
columns: [],
304+
};
305+
mockDatasource.getDatasourceSuggestionsForField.mockReturnValueOnce([
306+
{
307+
state: {},
308+
table: expectedTable,
309+
},
310+
]);
311+
mockVisualization.getSuggestions.mockReturnValueOnce([
312+
{
313+
score: 0.5,
314+
title: 'my title',
315+
state: {},
316+
datasourceSuggestionId: 0,
317+
},
318+
]);
319+
320+
instance.childAt(0).prop('onDrop')({
321+
name: '@timestamp',
322+
type: 'date',
323+
searchable: false,
324+
aggregatable: false,
325+
});
326+
327+
expect(mockDatasource.getDatasourceSuggestionsForField).toHaveBeenCalledTimes(1);
328+
expect(mockVisualization.getSuggestions).toHaveBeenCalledWith(
329+
expect.objectContaining({
330+
tables: [expectedTable],
331+
})
332+
);
333+
expect(mockDispatch).toHaveBeenCalledWith({
334+
type: 'SWITCH_VISUALIZATION',
335+
newVisualizationId: 'vis',
336+
initialState: {},
337+
datasourceState: {},
338+
});
339+
});
340+
341+
it('should immediately transition to the first suggestion if there are multiple', () => {
342+
mockDatasource.getDatasourceSuggestionsForField.mockReturnValueOnce([
343+
{
344+
state: {},
345+
table: {
346+
datasourceSuggestionId: 0,
347+
isMultiRow: true,
348+
columns: [],
349+
},
350+
},
351+
{
352+
state: {},
353+
table: {
354+
datasourceSuggestionId: 1,
355+
isMultiRow: true,
356+
columns: [],
357+
},
358+
},
359+
]);
360+
mockVisualization.getSuggestions.mockReturnValueOnce([
361+
{
362+
score: 0.8,
363+
title: 'first suggestion',
364+
state: {
365+
isFirst: true,
366+
},
367+
datasourceSuggestionId: 1,
368+
},
369+
{
370+
score: 0.5,
371+
title: 'second suggestion',
372+
state: {},
373+
datasourceSuggestionId: 0,
374+
},
375+
]);
376+
377+
instance.childAt(0).prop('onDrop')({
378+
name: '@timestamp',
379+
type: 'date',
380+
searchable: false,
381+
aggregatable: false,
382+
});
383+
384+
expect(mockDispatch).toHaveBeenCalledWith({
385+
type: 'SWITCH_VISUALIZATION',
386+
newVisualizationId: 'vis',
387+
initialState: {
388+
isFirst: true,
389+
},
390+
datasourceState: {},
391+
});
392+
});
393+
394+
it("should do nothing when the visualization can't use the suggestions", () => {
395+
mockDatasource.getDatasourceSuggestionsForField.mockReturnValueOnce([]);
396+
397+
instance.childAt(0).prop('onDrop')({
398+
name: '@timestamp',
399+
type: 'date',
400+
searchable: false,
401+
aggregatable: false,
402+
});
403+
404+
expect(mockDatasource.getDatasourceSuggestionsForField).toHaveBeenCalledTimes(1);
405+
expect(mockVisualization.getSuggestions).toHaveBeenCalledTimes(1);
406+
expect(mockDispatch).not.toHaveBeenCalled();
407+
});
408+
});
277409
});

x-pack/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ export function WorkspacePanel({
3737
dispatch,
3838
ExpressionRenderer: ExpressionRendererComponent,
3939
}: WorkspacePanelProps) {
40-
function onDrop() {
40+
function onDrop(item: unknown) {
4141
const datasourceSuggestions = activeDatasource.getDatasourceSuggestionsForField(
42-
datasourceState
42+
datasourceState,
43+
item
4344
);
4445

4546
const suggestions = getSuggestions(

x-pack/plugins/lens/public/editor_frame_plugin/mocks.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function createMockDatasource(): DatasourceMock {
3535
};
3636

3737
return {
38-
getDatasourceSuggestionsForField: jest.fn(_state => []),
38+
getDatasourceSuggestionsForField: jest.fn((_state, item) => []),
3939
getDatasourceSuggestionsFromCurrentState: jest.fn(_state => []),
4040
getPersistableState: jest.fn(),
4141
getPublicAPI: jest.fn((_state, _setState) => publicAPIMock),

x-pack/plugins/lens/public/indexpattern_plugin/indexpattern.test.tsx

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,175 @@ describe('IndexPattern Data Source', () => {
279279
});
280280
});
281281

282+
describe('#getDatasourceSuggestionsForField', () => {
283+
describe('with no previous selections', () => {
284+
let initialState: IndexPatternPrivateState;
285+
286+
beforeEach(async () => {
287+
initialState = await indexPatternDatasource.initialize({
288+
currentIndexPatternId: '1',
289+
columnOrder: [],
290+
columns: {},
291+
});
292+
});
293+
294+
it('should apply a bucketed aggregation for a string field', () => {
295+
const suggestions = indexPatternDatasource.getDatasourceSuggestionsForField(initialState, {
296+
name: 'source',
297+
type: 'string',
298+
aggregatable: true,
299+
searchable: true,
300+
});
301+
302+
expect(suggestions).toHaveLength(1);
303+
expect(suggestions[0].state).toEqual(
304+
expect.objectContaining({
305+
columnOrder: ['col1', 'col2'],
306+
columns: {
307+
col1: expect.objectContaining({
308+
operationType: 'terms',
309+
sourceField: 'source',
310+
}),
311+
col2: expect.objectContaining({
312+
operationType: 'count',
313+
sourceField: 'documents',
314+
}),
315+
},
316+
})
317+
);
318+
expect(suggestions[0].table).toEqual({
319+
datasourceSuggestionId: 0,
320+
isMultiRow: true,
321+
columns: [
322+
expect.objectContaining({
323+
columnId: 'col1',
324+
}),
325+
expect.objectContaining({
326+
columnId: 'col2',
327+
}),
328+
],
329+
});
330+
});
331+
332+
it('should apply a bucketed aggregation for a date field', () => {
333+
const suggestions = indexPatternDatasource.getDatasourceSuggestionsForField(initialState, {
334+
name: 'timestamp',
335+
type: 'date',
336+
aggregatable: true,
337+
searchable: true,
338+
});
339+
340+
expect(suggestions).toHaveLength(1);
341+
expect(suggestions[0].state).toEqual(
342+
expect.objectContaining({
343+
columnOrder: ['col1', 'col2'],
344+
columns: {
345+
col1: expect.objectContaining({
346+
operationType: 'date_histogram',
347+
sourceField: 'timestamp',
348+
}),
349+
col2: expect.objectContaining({
350+
operationType: 'count',
351+
sourceField: 'documents',
352+
}),
353+
},
354+
})
355+
);
356+
expect(suggestions[0].table).toEqual({
357+
datasourceSuggestionId: 0,
358+
isMultiRow: true,
359+
columns: [
360+
expect.objectContaining({
361+
columnId: 'col1',
362+
}),
363+
expect.objectContaining({
364+
columnId: 'col2',
365+
}),
366+
],
367+
});
368+
});
369+
370+
it('should select a metric for a number field', () => {
371+
const suggestions = indexPatternDatasource.getDatasourceSuggestionsForField(initialState, {
372+
name: 'bytes',
373+
type: 'number',
374+
aggregatable: true,
375+
searchable: true,
376+
});
377+
378+
expect(suggestions).toHaveLength(1);
379+
expect(suggestions[0].state).toEqual(
380+
expect.objectContaining({
381+
columnOrder: ['col1', 'col2'],
382+
columns: {
383+
col1: expect.objectContaining({
384+
sourceField: 'timestamp',
385+
operationType: 'date_histogram',
386+
}),
387+
col2: expect.objectContaining({
388+
sourceField: 'bytes',
389+
operationType: 'sum',
390+
}),
391+
},
392+
})
393+
);
394+
expect(suggestions[0].table).toEqual({
395+
datasourceSuggestionId: 0,
396+
isMultiRow: true,
397+
columns: [
398+
expect.objectContaining({
399+
columnId: 'col1',
400+
}),
401+
expect.objectContaining({
402+
columnId: 'col2',
403+
}),
404+
],
405+
});
406+
});
407+
});
408+
409+
describe('with a prior column', () => {
410+
let initialState: IndexPatternPrivateState;
411+
412+
beforeEach(async () => {
413+
initialState = await indexPatternDatasource.initialize(persistedState);
414+
});
415+
416+
it('should not suggest for string', () => {
417+
expect(
418+
indexPatternDatasource.getDatasourceSuggestionsForField(initialState, {
419+
name: 'source',
420+
type: 'string',
421+
aggregatable: true,
422+
searchable: true,
423+
})
424+
).toHaveLength(0);
425+
});
426+
427+
it('should not suggest for date', () => {
428+
expect(
429+
indexPatternDatasource.getDatasourceSuggestionsForField(initialState, {
430+
name: 'timestamp',
431+
type: 'date',
432+
aggregatable: true,
433+
searchable: true,
434+
})
435+
).toHaveLength(0);
436+
});
437+
438+
it('should not suggest for number', () => {
439+
expect(
440+
indexPatternDatasource.getDatasourceSuggestionsForField(initialState, {
441+
name: 'bytes',
442+
type: 'number',
443+
aggregatable: true,
444+
searchable: true,
445+
})
446+
).toHaveLength(0);
447+
});
448+
});
449+
});
450+
282451
describe('#getPublicAPI', () => {
283452
let publicAPI: DatasourcePublicAPI;
284453

0 commit comments

Comments
 (0)