Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Lens] Suggestion improvements #43688

Merged
merged 67 commits into from
Sep 2, 2019
Merged
Show file tree
Hide file tree
Changes from 66 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
33802fd
Lens basic metric visualization
chrisdavies Jun 19, 2019
f5c809a
Fix merge issues, localize expression help text
chrisdavies Jun 24, 2019
55a1fe3
Merge upstream
chrisdavies Jun 24, 2019
c159b4e
Add auto-scaling to the lens metric visualization
chrisdavies Jun 20, 2019
356e9b6
Fix unit tests broken by autoscale
chrisdavies Jun 20, 2019
6aea5dc
Move autoscale to the new Lens folder
chrisdavies Jun 24, 2019
1135b3d
Merge upstream
chrisdavies Jul 17, 2019
2dc623f
Add metric preview icon
chrisdavies Jul 17, 2019
bae8a6c
Fix metric vis tests
chrisdavies Jul 17, 2019
4ee8f63
Fix metric plugin imports
chrisdavies Jul 17, 2019
218cf00
Use the operation label as the metric title
chrisdavies Jul 17, 2019
a4f8c18
Merge upstream
chrisdavies Aug 8, 2019
59e19d9
Add metric suggestions, fix tests
chrisdavies Aug 9, 2019
eda40df
Back out suggestion changes, in lieu
chrisdavies Aug 9, 2019
55b59dd
Merge branch 'feature/lens' of github.com:elastic/kibana into lens/me…
chrisdavies Aug 9, 2019
77b02a2
Merge upstream
chrisdavies Aug 13, 2019
085210c
Fix metric autoscale logic
chrisdavies Aug 13, 2019
d00ddbb
Register metric as an embeddable
chrisdavies Aug 13, 2019
8436275
Fix metric autoscale flicker
chrisdavies Aug 13, 2019
4cbf3c8
Render mini metric in suggestions panel
chrisdavies Aug 13, 2019
5065a14
Cache the metric filterOperations function
chrisdavies Aug 13, 2019
b11021b
fix auto scaling edge cases
flash1293 Aug 14, 2019
d7bf71b
Merge pull request #9 from flash1293/lens/metric-scale-fixes
chrisdavies Aug 14, 2019
3d947ab
Merge branch 'feature/lens' of github.com:elastic/kibana into lens/me…
chrisdavies Aug 14, 2019
e46f935
Modify auto-scale to handle resize events
chrisdavies Aug 14, 2019
43ffe23
use format hints in metric vis
flash1293 Aug 15, 2019
981c577
start cleaning up suggestions
flash1293 Aug 15, 2019
7e1c550
Tweak metric to only scale down so far, and
chrisdavies Aug 16, 2019
859aa99
Fix lens metric tests
chrisdavies Aug 16, 2019
227d798
Merge pull request #10 from flash1293/lens/metric-scale-formathints
chrisdavies Aug 16, 2019
c85345e
start adding more suggestions
flash1293 Aug 19, 2019
7cdc774
Merge remote-tracking branch 'upstream/feature/lens' into lens/other-…
flash1293 Aug 19, 2019
c3beb1a
Merge remote-tracking branch 'upstream/feature/lens' into lens/metric…
flash1293 Aug 19, 2019
837c76c
remove unused imports
flash1293 Aug 19, 2019
b78a44f
Merge branch 'lens/metric-scale' into lens/other-suggestions
flash1293 Aug 19, 2019
e5ebcf8
work on suggestions
flash1293 Aug 19, 2019
400c352
work more on suggestions
flash1293 Aug 20, 2019
8d9cab5
work more on suggestions
flash1293 Aug 20, 2019
40b99f3
work more on suggestions
flash1293 Aug 20, 2019
e3667ce
Merge branch 'feature/lens' into lens/other-suggestions
flash1293 Aug 21, 2019
2c975bd
clean up tests and add new ones
flash1293 Aug 21, 2019
1dfa94e
Merge remote-tracking branch 'upstream/feature/lens' into lens/other-…
flash1293 Aug 21, 2019
30f27b5
remove isMetric
flash1293 Aug 21, 2019
16c3b03
area as default on time dimension
flash1293 Aug 21, 2019
d5452fd
fix bug in area chart for time
flash1293 Aug 21, 2019
9b3d76c
Merge branch 'feature/lens' into lens/other-suggestions
flash1293 Aug 23, 2019
ed11229
remove title form layer
flash1293 Aug 23, 2019
694e845
handle state in app
flash1293 Aug 23, 2019
61f3eba
Merge branch 'lens/fix-querybar-integration' into lens/other-suggestions
flash1293 Aug 23, 2019
3cec779
Merge remote-tracking branch 'upstream/feature/lens' into lens/fix-qu…
flash1293 Aug 23, 2019
54ad77c
fix isMetric usages
flash1293 Aug 23, 2019
82a4fc8
fix integration tests
flash1293 Aug 23, 2019
0dfa265
Merge branch 'lens/fix-querybar-integration' into lens/other-suggestions
flash1293 Aug 23, 2019
355f3fc
fix type errors
flash1293 Aug 23, 2019
3a6a23c
fix date handling on submit
flash1293 Aug 26, 2019
9a1ff84
Merge branch 'lens/fix-querybar-integration' into lens/other-suggestions
flash1293 Aug 26, 2019
7a31208
add new suggestion types
flash1293 Aug 26, 2019
4729ae4
fix test
flash1293 Aug 26, 2019
563205b
Merge branch 'lens/fix-querybar-integration' into lens/other-suggestions
flash1293 Aug 26, 2019
f35c722
do not suggest single tables
flash1293 Aug 26, 2019
25cdc68
remove unused import
flash1293 Aug 26, 2019
5cbca86
switch order of appending new string column
flash1293 Aug 27, 2019
d1d3647
Merge remote-tracking branch 'upstream/feature/lens' into lens/other-…
flash1293 Aug 27, 2019
aad9403
resolve merge conflicts
flash1293 Aug 27, 2019
d69a3db
Merge remote-tracking branch 'upstream/feature/lens' into lens/other-…
flash1293 Aug 28, 2019
f9bf7b1
fix merge conflicts
flash1293 Aug 28, 2019
8b7a9e0
code review
flash1293 Aug 30, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ describe('Datatable Visualization', () => {
const baseOperation: Operation = {
dataType: 'string',
isBucketed: true,
isMetric: false,
flash1293 marked this conversation as resolved.
Show resolved Hide resolved
label: '',
};
expect(filterOperations({ ...baseOperation })).toEqual(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,42 +127,57 @@ export const datatableVisualization: Visualization<

getSuggestions({
tables,
state,
}: SuggestionRequest<DatatableVisualizationState>): Array<
VisualizationSuggestion<DatatableVisualizationState>
> {
const maxColumnCount = Math.max.apply(undefined, tables.map(table => table.columns.length));
return tables.map(table => {
const title = i18n.translate('xpack.lens.datatable.visualizationOf', {
defaultMessage: 'Table: {operations}',
values: {
operations: table.columns
.map(col => col.operation.label)
.join(
i18n.translate('xpack.lens.datatable.conjunctionSign', {
defaultMessage: ' & ',
description:
'A character that can be used for conjunction of multiple enumarated items. Make sure to include spaces around it if needed.',
})
),
},
});

return {
title,
// largest possible table will have a score of 0.2, less columns reduce score
score: (table.columns.length / maxColumnCount) * 0.2,
datasourceSuggestionId: table.datasourceSuggestionId,
state: {
layers: [
{
layerId: table.layerId,
columns: table.columns.map(col => col.columnId),
return (
tables
// don't suggest current table if visualization is active
.filter(({ changeType }) => !state || changeType !== 'unchanged')
.map(table => {
const title =
table.changeType === 'unchanged'
? i18n.translate('xpack.lens.datatable.suggestionLabel', {
defaultMessage: 'As table',
})
: i18n.translate('xpack.lens.datatable.visualizationOf', {
defaultMessage: 'Table {operations}',
values: {
operations:
table.label ||
table.columns
.map(col => col.operation.label)
.join(
i18n.translate('xpack.lens.datatable.conjunctionSign', {
defaultMessage: ' & ',
description:
'A character that can be used for conjunction of multiple enumarated items. Make sure to include spaces around it if needed.',
})
),
},
});

return {
title,
// largest possible table will have a score of 0.2, less columns reduce score
flash1293 marked this conversation as resolved.
Show resolved Hide resolved
score: (table.columns.length / maxColumnCount) * 0.2,
datasourceSuggestionId: table.datasourceSuggestionId,
state: {
layers: [
{
layerId: table.layerId,
columns: table.columns.map(col => col.columnId),
},
],
},
],
},
previewIcon: 'visTable',
};
});
previewIcon: 'visTable',
// dont show suggestions for reduced versions or single-line tables
hide: table.changeType === 'reduced' || !table.isMultiRow,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of hide, should we just make this part of the filter on line 138?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do this, the single-row table won't be re-used if you switch e.g. from a metric visualization to a table visualization. There is a point to drop the metric if you do that, but I think it's nice to carry over as much as possible.

};
})
);
},

renderConfigPanel: (domElement, props) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ describe('chart_switch', () => {
datasourceSuggestionId: 0,
isMultiRow: true,
layerId: 'a',
changeType: 'unchanged',
},
},
]);
Expand Down Expand Up @@ -205,7 +206,6 @@ describe('chart_switch', () => {
label: '',
dataType: 'string',
isBucketed: true,
isMetric: false,
},
},
{
Expand All @@ -214,13 +214,13 @@ describe('chart_switch', () => {
label: '',
dataType: 'number',
isBucketed: false,
isMetric: true,
},
},
],
datasourceSuggestionId: 0,
layerId: 'first',
isMultiRow: true,
changeType: 'unchanged',
},
},
]);
Expand Down Expand Up @@ -435,7 +435,6 @@ describe('chart_switch', () => {
label: '',
dataType: 'string',
isBucketed: true,
isMetric: false,
},
},
{
Expand All @@ -444,13 +443,13 @@ describe('chart_switch', () => {
label: '',
dataType: 'number',
isBucketed: false,
isMetric: true,
},
},
],
datasourceSuggestionId: 0,
layerId: 'a',
isMultiRow: true,
changeType: 'unchanged',
},
},
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ export function ChartSwitch(props: Props) {
visualizationMap: { [visualizationId]: newVisualization },
activeVisualizationId: props.visualizationId,
visualizationState: props.visualizationState,
}).filter(suggestion => {
// don't use extended versions of current data table on switching between visualizations
// to avoid confusing the user.
return suggestion.changeType !== 'extended';
})[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks to me like this could now be undefined if you filter out all suggestions- can you handle that case specifically?

Copy link
Contributor Author

@flash1293 flash1293 Aug 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could also happen in the old version of the code (e.g. if the datasource doesn't return any suggestions) and is already handled - the return object checks whether topSuggestion exists before acting on it.

To make this more explicit I moved the call to a separate function with an explicitly nullable return type

function getTopSuggestion(
  props: Props,
  visualizationId: string,
  newVisualization: Visualization<unknown, unknown>
): Suggestion | undefined {
  return getSuggestions({
    datasourceMap: props.datasourceMap,
    datasourceStates: props.datasourceStates,
    visualizationMap: { [visualizationId]: newVisualization },
    activeVisualizationId: props.visualizationId,
    visualizationState: props.visualizationState,
  }).filter(suggestion => {
    // don't use extended versions of current data table on switching between visualizations
    // to avoid confusing the user.
    return suggestion.changeType !== 'extended';
  })[0];
}


let dataLoss: VisualizationSelection['dataLoss'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ function generateSuggestion(datasourceSuggestionId = 1, state = {}): DatasourceS
datasourceSuggestionId,
isMultiRow: true,
layerId: 'first',
changeType: 'unchanged',
},
};
}
Expand Down Expand Up @@ -907,6 +908,7 @@ describe('editor_frame', () => {
datasourceSuggestionId: 0,
isMultiRow: true,
layerId: 'first',
changeType: 'unchanged',
},
},
]);
Expand Down Expand Up @@ -1068,18 +1070,32 @@ describe('editor_frame', () => {
expect(mockVisualization2.getSuggestions).toHaveBeenCalled();
});

it('should display top 3 suggestions in descending order', async () => {
it('should display top 5 suggestions in descending order', async () => {
const instance = mount(
<EditorFrame
{...getDefaultProps()}
visualizationMap={{
testVis: {
...mockVisualization,
getSuggestions: () => [
{
datasourceSuggestionId: 0,
score: 0.1,
state: {},
title: 'Suggestion6',
previewIcon: 'empty',
},
{
datasourceSuggestionId: 0,
score: 0.5,
state: {},
title: 'Suggestion3',
previewIcon: 'empty',
},
{
datasourceSuggestionId: 0,
score: 0.7,
state: {},
title: 'Suggestion2',
previewIcon: 'empty',
},
Expand All @@ -1099,14 +1115,14 @@ describe('editor_frame', () => {
datasourceSuggestionId: 0,
score: 0.4,
state: {},
title: 'Suggestion4',
title: 'Suggestion5',
previewIcon: 'empty',
},
{
datasourceSuggestionId: 0,
score: 0.45,
state: {},
title: 'Suggestion3',
title: 'Suggestion4',
previewIcon: 'empty',
},
],
Expand All @@ -1133,7 +1149,7 @@ describe('editor_frame', () => {
.find('[data-test-subj="lnsSuggestion"]')
.find(EuiPanel)
.map(el => el.parents(EuiToolTip).prop('content'))
).toEqual(['Suggestion1', 'Suggestion2', 'Suggestion3']);
).toEqual(['Suggestion1', 'Suggestion2', 'Suggestion3', 'Suggestion4', 'Suggestion5']);
});

it('should switch to suggested visualization', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ const generateSuggestion = (
layerId: string = 'first'
): DatasourceSuggestion => ({
state,
table: { datasourceSuggestionId, columns: [], isMultiRow: false, layerId },
table: {
datasourceSuggestionId,
columns: [],
isMultiRow: false,
layerId,
changeType: 'unchanged',
},
});

let datasourceMap: Record<string, DatasourceMock>;
Expand Down Expand Up @@ -233,12 +239,14 @@ describe('suggestion helpers', () => {
columns: [],
isMultiRow: true,
layerId: 'first',
changeType: 'unchanged',
};
const table2: TableSuggestion = {
datasourceSuggestionId: 1,
columns: [],
isMultiRow: true,
layerId: 'first',
changeType: 'unchanged',
};
datasourceMap.mock.getDatasourceSuggestionsFromCurrentState.mockReturnValue([
{ state: {}, table: table1 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import _ from 'lodash';
import { Ast } from '@kbn/interpreter/common';
import { Visualization, Datasource, FramePublicAPI } from '../../types';
import { Visualization, Datasource, FramePublicAPI, TableChangeType } from '../../types';
import { Action } from './state_management';

export interface Suggestion {
Expand All @@ -20,6 +20,8 @@ export interface Suggestion {
visualizationState: unknown;
previewExpression?: Ast | string;
previewIcon: string;
hide?: boolean;
changeType: TableChangeType;
}

/**
Expand Down Expand Up @@ -100,6 +102,7 @@ export function getSuggestions({
datasourceState: datasourceSuggestion.state,
datasourceId: datasourceSuggestion.datasourceId,
columns: datasourceSuggestion.table.columns.length,
changeType: datasourceSuggestion.table.changeType,
};
});
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,23 @@ describe('suggestion_panel', () => {
expect(passedExpression).toMatchInlineSnapshot(`
Object {
"chain": Array [
Object {
"arguments": Object {},
"function": "kibana",
"type": "function",
},
Object {
"arguments": Object {
"query": Array [
"{\\"query\\":\\"\\",\\"language\\":\\"lucene\\"}",
],
"timeRange": Array [
"{\\"from\\":\\"now-7d\\",\\"to\\":\\"now\\"}",
],
},
"function": "kibana_context",
"type": "function",
},
Object {
"arguments": Object {
"layerIds": Array [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import { Action } from './state_management';
import { Datasource, Visualization, FramePublicAPI } from '../../types';
import { getSuggestions, Suggestion, switchToSuggestion } from './suggestion_helpers';
import { ExpressionRenderer } from '../../../../../../../src/legacy/core_plugins/data/public';
import { prependDatasourceExpression } from './expression_helpers';
import { prependDatasourceExpression, prependKibanaContext } from './expression_helpers';
import { debouncedComponent } from '../../debounced_component';

const MAX_SUGGESTIONS_DISPLAYED = 3;
const MAX_SUGGESTIONS_DISPLAYED = 5;

export interface SuggestionPanelProps {
activeDatasourceId: string | null;
Expand Down Expand Up @@ -122,7 +122,9 @@ function InnerSuggestionPanel({
visualizationMap,
activeVisualizationId,
visualizationState,
}).slice(0, MAX_SUGGESTIONS_DISPLAYED);
})
.filter(suggestion => !suggestion.hide)
.slice(0, MAX_SUGGESTIONS_DISPLAYED);

if (suggestions.length === 0) {
return null;
Expand All @@ -139,14 +141,13 @@ function InnerSuggestionPanel({
</h3>
</EuiTitle>
<div className="lnsSuggestionsPanel__suggestions">
{suggestions.map(suggestion => {
const previewExpression = suggestion.previewExpression
? prependDatasourceExpression(
suggestion.previewExpression,
datasourceMap,
datasourceStates
)
: null;
{suggestions.map((suggestion: Suggestion) => {
const previewExpression = preparePreviewExpression(
suggestion,
datasourceMap,
datasourceStates,
frame
);
return (
<SuggestionPreview
suggestion={suggestion}
Expand All @@ -162,3 +163,35 @@ function InnerSuggestionPanel({
</div>
);
}
function preparePreviewExpression(
suggestion: Suggestion,
datasourceMap: Record<string, Datasource<unknown, unknown>>,
datasourceStates: Record<string, { isLoading: boolean; state: unknown }>,
framePublicAPI: FramePublicAPI
) {
if (!suggestion.previewExpression) return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like we should not be calling preparePreviewExpression if previewExpression is falsy. Maybe we should move this check to the filter clause on line 126?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved, but it didn't really make things much simpler... The problem is that there are a lot of conditions that can cause the expression to become null, but getting rid of one of them is probably worth it.


const expressionWithDatasource = prependDatasourceExpression(
suggestion.previewExpression,
datasourceMap,
suggestion.datasourceId
? {
...datasourceStates,
[suggestion.datasourceId]: {
isLoading: false,
state: suggestion.datasourceState,
},
}
: datasourceStates
);

const expressionContext = {
query: framePublicAPI.query,
timeRange: {
from: framePublicAPI.dateRange.fromDate,
to: framePublicAPI.dateRange.toDate,
},
};

return prependKibanaContext(expressionWithDatasource, expressionContext);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Time ranges + queries in suggestions LGTM!

}
Loading