mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* Add loading indicator to Lens workspace panel * [Expressions] [Lens] Handle loading and errors in ExpressionRenderer * Using loading$ observable and improve tests * [Lens] Fix layer crash and improve layer suggestions * Using CSS and to handle layout of expression renderer Added TODO for using chart loader when area is completely empty * Improve error handling and simplify code * Fix cleanup behavior * Fix double render and prevent error cases in xy chart * Fix context for use in dashboards * Remove className from expression rendere component * Improve handling of additional interpreter args * More layout fixes - Hide chart if Empty not Loading - Fix relative positioning for progress bar since className is no longer passed (super hacky) * Build suggestions that remove layers * Update tests and add keptLayerIds everywhere * Fix bug where datatable would accept multi-layer suggestions * Build more suggestions that work with metric/datatable * Fix issue with chart switching from empty * Fix datatable multiple layer issue
This commit is contained in:
parent
398beccd4d
commit
2562c5866b
18 changed files with 411 additions and 190 deletions
|
@ -12,7 +12,7 @@ import {
|
|||
DataTableLayer,
|
||||
} from './visualization';
|
||||
import { mount } from 'enzyme';
|
||||
import { Operation, DataType, FramePublicAPI } from '../types';
|
||||
import { Operation, DataType, FramePublicAPI, TableSuggestionColumn } from '../types';
|
||||
import { generateId } from '../id_generator';
|
||||
|
||||
jest.mock('../id_generator');
|
||||
|
@ -72,6 +72,112 @@ describe('Datatable Visualization', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#getSuggestions', () => {
|
||||
function numCol(columnId: string): TableSuggestionColumn {
|
||||
return {
|
||||
columnId,
|
||||
operation: {
|
||||
dataType: 'number',
|
||||
label: `Avg ${columnId}`,
|
||||
isBucketed: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function strCol(columnId: string): TableSuggestionColumn {
|
||||
return {
|
||||
columnId,
|
||||
operation: {
|
||||
dataType: 'string',
|
||||
label: `Top 5 ${columnId}`,
|
||||
isBucketed: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
it('should accept a single-layer suggestion', () => {
|
||||
const suggestions = datatableVisualization.getSuggestions({
|
||||
state: {
|
||||
layers: [{ layerId: 'first', columns: ['col1'] }],
|
||||
},
|
||||
table: {
|
||||
isMultiRow: true,
|
||||
layerId: 'first',
|
||||
changeType: 'initial',
|
||||
columns: [numCol('col1'), strCol('col2')],
|
||||
},
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(suggestions.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should not make suggestions when the table is unchanged', () => {
|
||||
const suggestions = datatableVisualization.getSuggestions({
|
||||
state: {
|
||||
layers: [{ layerId: 'first', columns: ['col1'] }],
|
||||
},
|
||||
table: {
|
||||
isMultiRow: true,
|
||||
layerId: 'first',
|
||||
changeType: 'unchanged',
|
||||
columns: [numCol('col1')],
|
||||
},
|
||||
keptLayerIds: ['first'],
|
||||
});
|
||||
|
||||
expect(suggestions).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not make suggestions when multiple layers are involved', () => {
|
||||
const suggestions = datatableVisualization.getSuggestions({
|
||||
state: {
|
||||
layers: [{ layerId: 'first', columns: ['col1'] }],
|
||||
},
|
||||
table: {
|
||||
isMultiRow: true,
|
||||
layerId: 'first',
|
||||
changeType: 'unchanged',
|
||||
columns: [numCol('col1')],
|
||||
},
|
||||
keptLayerIds: ['first', 'second'],
|
||||
});
|
||||
|
||||
expect(suggestions).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not make suggestions when the suggestion keeps a different layer', () => {
|
||||
const suggestions = datatableVisualization.getSuggestions({
|
||||
state: {
|
||||
layers: [{ layerId: 'older', columns: ['col1'] }],
|
||||
},
|
||||
table: {
|
||||
isMultiRow: true,
|
||||
layerId: 'newer',
|
||||
changeType: 'initial',
|
||||
columns: [numCol('col1'), strCol('col2')],
|
||||
},
|
||||
keptLayerIds: ['older'],
|
||||
});
|
||||
|
||||
expect(suggestions).toEqual([]);
|
||||
});
|
||||
|
||||
it('should suggest unchanged tables when the state is not passed in', () => {
|
||||
const suggestions = datatableVisualization.getSuggestions({
|
||||
table: {
|
||||
isMultiRow: true,
|
||||
layerId: 'first',
|
||||
changeType: 'unchanged',
|
||||
columns: [numCol('col1')],
|
||||
},
|
||||
keptLayerIds: ['first'],
|
||||
});
|
||||
|
||||
expect(suggestions.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DataTableLayer', () => {
|
||||
it('allows all kinds of operations', () => {
|
||||
const setState = jest.fn();
|
||||
|
|
|
@ -134,10 +134,15 @@ export const datatableVisualization: Visualization<
|
|||
getSuggestions({
|
||||
table,
|
||||
state,
|
||||
keptLayerIds,
|
||||
}: SuggestionRequest<DatatableVisualizationState>): Array<
|
||||
VisualizationSuggestion<DatatableVisualizationState>
|
||||
> {
|
||||
if (state && table.changeType === 'unchanged') {
|
||||
if (
|
||||
keptLayerIds.length > 1 ||
|
||||
(keptLayerIds.length && table.layerId !== keptLayerIds[0]) ||
|
||||
(state && table.changeType === 'unchanged')
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
const title =
|
||||
|
|
|
@ -94,6 +94,7 @@ describe('chart_switch', () => {
|
|||
layerId: 'a',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: ['a'],
|
||||
},
|
||||
]);
|
||||
return {
|
||||
|
@ -180,6 +181,8 @@ describe('chart_switch', () => {
|
|||
|
||||
switchTo('subvisB', component);
|
||||
|
||||
expect(frame.removeLayers).toHaveBeenCalledWith(['a']);
|
||||
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
initialState: 'visB initial state',
|
||||
newVisualizationId: 'visB',
|
||||
|
@ -219,6 +222,7 @@ describe('chart_switch', () => {
|
|||
isMultiRow: true,
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
},
|
||||
]);
|
||||
datasourceMap.testDatasource.publicAPIMock.getTableSpec.mockReturnValue([
|
||||
|
@ -307,11 +311,7 @@ describe('chart_switch', () => {
|
|||
|
||||
it('should not indicate data loss if visualization is not changed', () => {
|
||||
const dispatch = jest.fn();
|
||||
const removeLayers = jest.fn();
|
||||
const frame = {
|
||||
...mockFrame(['a', 'b', 'c']),
|
||||
removeLayers,
|
||||
};
|
||||
const frame = mockFrame(['a', 'b', 'c']);
|
||||
const visualizations = mockVisualizations();
|
||||
const switchVisualizationType = jest.fn(() => 'therebedragons');
|
||||
|
||||
|
@ -332,30 +332,6 @@ describe('chart_switch', () => {
|
|||
expect(getMenuItem('subvisC2', component).prop('betaBadgeIconType')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should remove unused layers', () => {
|
||||
const removeLayers = jest.fn();
|
||||
const frame = {
|
||||
...mockFrame(['a', 'b', 'c']),
|
||||
removeLayers,
|
||||
};
|
||||
const component = mount(
|
||||
<ChartSwitch
|
||||
visualizationId="visA"
|
||||
visualizationState={{}}
|
||||
visualizationMap={mockVisualizations()}
|
||||
dispatch={jest.fn()}
|
||||
framePublicAPI={frame}
|
||||
datasourceMap={mockDatasourceMap()}
|
||||
datasourceStates={mockDatasourceStates()}
|
||||
/>
|
||||
);
|
||||
|
||||
switchTo('subvisB', component);
|
||||
|
||||
expect(removeLayers).toHaveBeenCalledTimes(1);
|
||||
expect(removeLayers).toHaveBeenCalledWith(['b', 'c']);
|
||||
});
|
||||
|
||||
it('should remove all layers if there is no suggestion', () => {
|
||||
const dispatch = jest.fn();
|
||||
const visualizations = mockVisualizations();
|
||||
|
@ -378,15 +354,24 @@ describe('chart_switch', () => {
|
|||
|
||||
expect(frame.removeLayers).toHaveBeenCalledTimes(1);
|
||||
expect(frame.removeLayers).toHaveBeenCalledWith(['a', 'b', 'c']);
|
||||
|
||||
expect(visualizations.visB.getSuggestions).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
keptLayerIds: ['a'],
|
||||
})
|
||||
);
|
||||
|
||||
expect(dispatch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'SWITCH_VISUALIZATION',
|
||||
initialState: 'visB initial state',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not remove layers if the visualization is not changing', () => {
|
||||
const dispatch = jest.fn();
|
||||
const removeLayers = jest.fn();
|
||||
const frame = {
|
||||
...mockFrame(['a', 'b', 'c']),
|
||||
removeLayers,
|
||||
};
|
||||
const frame = mockFrame(['a', 'b', 'c']);
|
||||
const visualizations = mockVisualizations();
|
||||
const switchVisualizationType = jest.fn(() => 'therebedragons');
|
||||
|
||||
|
@ -405,7 +390,6 @@ describe('chart_switch', () => {
|
|||
);
|
||||
|
||||
switchTo('subvisC2', component);
|
||||
expect(removeLayers).not.toHaveBeenCalled();
|
||||
expect(switchVisualizationType).toHaveBeenCalledWith('subvisC2', 'therebegriffins');
|
||||
expect(dispatch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -447,6 +431,7 @@ describe('chart_switch', () => {
|
|||
isMultiRow: true,
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
|
@ -88,6 +88,10 @@ export function ChartSwitch(props: Props) {
|
|||
},
|
||||
'SWITCH_VISUALIZATION'
|
||||
);
|
||||
|
||||
if (!selection.datasourceId || selection.dataLoss === 'everything') {
|
||||
props.framePublicAPI.removeLayers(Object.keys(props.framePublicAPI.datasourceLayers));
|
||||
}
|
||||
};
|
||||
|
||||
function getSelection(
|
||||
|
|
|
@ -35,6 +35,7 @@ function generateSuggestion(state = {}): DatasourceSuggestion {
|
|||
layerId: 'first',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: ['first'],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -928,6 +929,7 @@ describe('editor_frame', () => {
|
|||
layerId: 'first',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -1073,6 +1075,7 @@ describe('editor_frame', () => {
|
|||
isMultiRow: true,
|
||||
layerId: 'first',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
},
|
||||
]);
|
||||
mount(
|
||||
|
|
|
@ -16,6 +16,7 @@ const generateSuggestion = (state = {}, layerId: string = 'first'): DatasourceSu
|
|||
layerId,
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [layerId],
|
||||
});
|
||||
|
||||
let datasourceMap: Record<string, DatasourceMock>;
|
||||
|
@ -235,8 +236,8 @@ describe('suggestion helpers', () => {
|
|||
changeType: 'unchanged',
|
||||
};
|
||||
datasourceMap.mock.getDatasourceSuggestionsFromCurrentState.mockReturnValue([
|
||||
{ state: {}, table: table1 },
|
||||
{ state: {}, table: table2 },
|
||||
{ state: {}, table: table1, keptLayerIds: ['first'] },
|
||||
{ state: {}, table: table2, keptLayerIds: ['first'] },
|
||||
]);
|
||||
getSuggestions({
|
||||
visualizationMap: {
|
||||
|
@ -343,45 +344,4 @@ describe('suggestion helpers', () => {
|
|||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should drop other layers only on visualization switch', () => {
|
||||
const mockVisualization1 = createMockVisualization();
|
||||
const mockVisualization2 = createMockVisualization();
|
||||
datasourceMap.mock.getDatasourceSuggestionsFromCurrentState.mockReturnValue([
|
||||
generateSuggestion(),
|
||||
]);
|
||||
datasourceMap.mock.getLayers.mockReturnValue(['first', 'second']);
|
||||
const suggestions = getSuggestions({
|
||||
visualizationMap: {
|
||||
vis1: {
|
||||
...mockVisualization1,
|
||||
getSuggestions: () => [
|
||||
{
|
||||
score: 0.8,
|
||||
title: 'Test2',
|
||||
state: {},
|
||||
previewIcon: 'empty',
|
||||
},
|
||||
],
|
||||
},
|
||||
vis2: {
|
||||
...mockVisualization2,
|
||||
getSuggestions: () => [
|
||||
{
|
||||
score: 0.6,
|
||||
title: 'Test3',
|
||||
state: {},
|
||||
previewIcon: 'empty',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
activeVisualizationId: 'vis1',
|
||||
visualizationState: {},
|
||||
datasourceMap,
|
||||
datasourceStates,
|
||||
});
|
||||
expect(suggestions[0].keptLayerIds).toEqual(['first', 'second']);
|
||||
expect(suggestions[1].keptLayerIds).toEqual(['first']);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
FramePublicAPI,
|
||||
TableChangeType,
|
||||
TableSuggestion,
|
||||
DatasourceSuggestion,
|
||||
} from '../../types';
|
||||
import { Action } from './state_management';
|
||||
|
||||
|
@ -20,7 +21,6 @@ export interface Suggestion {
|
|||
visualizationId: string;
|
||||
datasourceState?: unknown;
|
||||
datasourceId?: string;
|
||||
keptLayerIds: string[];
|
||||
columns: number;
|
||||
score: number;
|
||||
title: string;
|
||||
|
@ -29,6 +29,7 @@ export interface Suggestion {
|
|||
previewIcon: IconType;
|
||||
hide?: boolean;
|
||||
changeType: TableChangeType;
|
||||
keptLayerIds: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,12 +65,6 @@ export function getSuggestions({
|
|||
([datasourceId]) => datasourceStates[datasourceId] && !datasourceStates[datasourceId].isLoading
|
||||
);
|
||||
|
||||
const allLayerIds = _.flatten(
|
||||
datasources.map(([datasourceId, datasource]) =>
|
||||
datasource.getLayers(datasourceStates[datasourceId].state)
|
||||
)
|
||||
);
|
||||
|
||||
// Collect all table suggestions from available datasources
|
||||
const datasourceTableSuggestions = _.flatten(
|
||||
datasources.map(([datasourceId, datasource]) => {
|
||||
|
@ -90,17 +85,12 @@ export function getSuggestions({
|
|||
const table = datasourceSuggestion.table;
|
||||
const currentVisualizationState =
|
||||
visualizationId === activeVisualizationId ? visualizationState : undefined;
|
||||
const keptLayerIds =
|
||||
visualizationId !== activeVisualizationId
|
||||
? [datasourceSuggestion.table.layerId]
|
||||
: allLayerIds;
|
||||
return getVisualizationSuggestions(
|
||||
visualization,
|
||||
table,
|
||||
visualizationId,
|
||||
datasourceSuggestion,
|
||||
currentVisualizationState,
|
||||
keptLayerIds
|
||||
currentVisualizationState
|
||||
);
|
||||
})
|
||||
)
|
||||
|
@ -118,20 +108,20 @@ function getVisualizationSuggestions(
|
|||
visualization: Visualization<unknown, unknown>,
|
||||
table: TableSuggestion,
|
||||
visualizationId: string,
|
||||
datasourceSuggestion: { datasourceId: string; state: unknown; table: TableSuggestion },
|
||||
currentVisualizationState: unknown,
|
||||
keptLayerIds: string[]
|
||||
datasourceSuggestion: DatasourceSuggestion & { datasourceId: string },
|
||||
currentVisualizationState: unknown
|
||||
) {
|
||||
return visualization
|
||||
.getSuggestions({
|
||||
table,
|
||||
state: currentVisualizationState,
|
||||
keptLayerIds: datasourceSuggestion.keptLayerIds,
|
||||
})
|
||||
.map(({ state, ...visualizationSuggestion }) => ({
|
||||
...visualizationSuggestion,
|
||||
visualizationId,
|
||||
visualizationState: state,
|
||||
keptLayerIds,
|
||||
keptLayerIds: datasourceSuggestion.keptLayerIds,
|
||||
datasourceState: datasourceSuggestion.state,
|
||||
datasourceId: datasourceSuggestion.datasourceId,
|
||||
columns: table.columns.length,
|
||||
|
@ -144,7 +134,7 @@ export function switchToSuggestion(
|
|||
dispatch: (action: Action) => void,
|
||||
suggestion: Pick<
|
||||
Suggestion,
|
||||
'visualizationId' | 'visualizationState' | 'datasourceState' | 'datasourceId' | 'keptLayerIds'
|
||||
'visualizationId' | 'visualizationState' | 'datasourceState' | 'datasourceId'
|
||||
>,
|
||||
type: 'SWITCH_VISUALIZATION' | 'SELECT_SUGGESTION' = 'SELECT_SUGGESTION'
|
||||
) {
|
||||
|
@ -156,10 +146,4 @@ export function switchToSuggestion(
|
|||
datasourceId: suggestion.datasourceId!,
|
||||
};
|
||||
dispatch(action);
|
||||
const layerIds = Object.keys(frame.datasourceLayers).filter(id => {
|
||||
return !suggestion.keptLayerIds.includes(id);
|
||||
});
|
||||
if (layerIds.length > 0) {
|
||||
frame.removeLayers(layerIds);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -218,36 +218,6 @@ describe('suggestion_panel', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should remove unused layers if suggestion is clicked', () => {
|
||||
defaultProps.frame.datasourceLayers.a = mockDatasource.publicAPIMock;
|
||||
defaultProps.frame.datasourceLayers.b = mockDatasource.publicAPIMock;
|
||||
const wrapper = mount(
|
||||
<SuggestionPanel
|
||||
{...defaultProps}
|
||||
stagedPreview={{ visualization: { state: {}, activeId: 'vis' }, datasourceStates: {} }}
|
||||
activeVisualizationId="vis2"
|
||||
/>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
wrapper
|
||||
.find('button[data-test-subj="lnsSuggestion"]')
|
||||
.at(1)
|
||||
.simulate('click');
|
||||
});
|
||||
|
||||
wrapper.update();
|
||||
|
||||
act(() => {
|
||||
wrapper
|
||||
.find('[data-test-subj="lensSubmitSuggestion"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
});
|
||||
|
||||
expect(defaultProps.frame.removeLayers).toHaveBeenCalledWith(['b']);
|
||||
});
|
||||
|
||||
it('should render preview expression if there is one', () => {
|
||||
mockDatasource.getLayers.mockReturnValue(['first']);
|
||||
(getSuggestions as jest.Mock).mockReturnValue([
|
||||
|
|
|
@ -574,6 +574,7 @@ describe('workspace_panel', () => {
|
|||
{
|
||||
state: {},
|
||||
table: expectedTable,
|
||||
keptLayerIds: [],
|
||||
},
|
||||
]);
|
||||
mockVisualization.getSuggestions.mockReturnValueOnce([
|
||||
|
@ -613,6 +614,7 @@ describe('workspace_panel', () => {
|
|||
columns: [],
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
},
|
||||
]);
|
||||
mockVisualization.getSuggestions.mockReturnValueOnce([
|
||||
|
@ -639,6 +641,7 @@ describe('workspace_panel', () => {
|
|||
columns: [],
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
},
|
||||
]);
|
||||
mockVisualization2.getSuggestions.mockReturnValueOnce([
|
||||
|
@ -665,6 +668,7 @@ describe('workspace_panel', () => {
|
|||
columns: [],
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
},
|
||||
]);
|
||||
mockVisualization.getSuggestions.mockReturnValueOnce([
|
||||
|
@ -694,6 +698,7 @@ describe('workspace_panel', () => {
|
|||
layerId: '1',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
},
|
||||
{
|
||||
state: {},
|
||||
|
@ -703,6 +708,7 @@ describe('workspace_panel', () => {
|
|||
layerId: '1',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
},
|
||||
]);
|
||||
mockVisualization.getSuggestions.mockReturnValueOnce([
|
||||
|
|
|
@ -260,6 +260,7 @@ export function getIndexPatternDatasource({
|
|||
state,
|
||||
layerId: props.layerId,
|
||||
onError: onIndexPatternLoadError,
|
||||
replaceIfPossible: true,
|
||||
});
|
||||
}}
|
||||
{...props}
|
||||
|
|
|
@ -562,6 +562,62 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a new layer and replaces layer if no match is found', () => {
|
||||
const suggestions = getDatasourceSuggestionsForField(stateWithEmptyLayer(), '2', {
|
||||
name: 'source',
|
||||
type: 'string',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
});
|
||||
|
||||
expect(suggestions).toContainEqual(
|
||||
expect.objectContaining({
|
||||
state: expect.objectContaining({
|
||||
layers: {
|
||||
previousLayer: expect.objectContaining({
|
||||
indexPatternId: '1',
|
||||
}),
|
||||
id1: expect.objectContaining({
|
||||
indexPatternId: '2',
|
||||
}),
|
||||
},
|
||||
}),
|
||||
table: {
|
||||
changeType: 'initial',
|
||||
label: undefined,
|
||||
isMultiRow: true,
|
||||
columns: expect.arrayContaining([]),
|
||||
layerId: 'id1',
|
||||
},
|
||||
keptLayerIds: ['previousLayer'],
|
||||
})
|
||||
);
|
||||
|
||||
expect(suggestions).toContainEqual(
|
||||
expect.objectContaining({
|
||||
state: expect.objectContaining({
|
||||
layers: {
|
||||
id1: expect.objectContaining({
|
||||
indexPatternId: '2',
|
||||
}),
|
||||
},
|
||||
}),
|
||||
table: {
|
||||
changeType: 'initial',
|
||||
label: undefined,
|
||||
isMultiRow: false,
|
||||
columns: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
columnId: expect.any(String),
|
||||
}),
|
||||
]),
|
||||
layerId: 'id1',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('suggesting extensions to non-empty tables', () => {
|
||||
|
@ -979,12 +1035,25 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
};
|
||||
|
||||
const result = getDatasourceSuggestionsFromCurrentState(state);
|
||||
|
||||
expect(result).toContainEqual(
|
||||
expect.objectContaining({
|
||||
table: expect.objectContaining({
|
||||
isMultiRow: true,
|
||||
changeType: 'unchanged',
|
||||
label: undefined,
|
||||
layerId: 'first',
|
||||
}),
|
||||
keptLayerIds: ['first', 'second'],
|
||||
})
|
||||
);
|
||||
|
||||
expect(result).toContainEqual(
|
||||
expect.objectContaining({
|
||||
table: {
|
||||
isMultiRow: true,
|
||||
changeType: 'unchanged',
|
||||
label: undefined,
|
||||
changeType: 'layers',
|
||||
label: 'Show only layer 1',
|
||||
columns: [
|
||||
{
|
||||
columnId: 'col1',
|
||||
|
@ -1005,8 +1074,8 @@ describe('IndexPattern Data Source suggestions', () => {
|
|||
expect.objectContaining({
|
||||
table: {
|
||||
isMultiRow: true,
|
||||
changeType: 'unchanged',
|
||||
label: undefined,
|
||||
changeType: 'layers',
|
||||
label: 'Show only layer 2',
|
||||
columns: [
|
||||
{
|
||||
columnId: 'cola',
|
||||
|
|
|
@ -81,6 +81,8 @@ function buildSuggestion({
|
|||
changeType,
|
||||
label,
|
||||
},
|
||||
|
||||
keptLayerIds: Object.keys(state.layers),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -93,10 +95,13 @@ export function getDatasourceSuggestionsForField(
|
|||
const layerIds = layers.filter(id => state.layers[id].indexPatternId === indexPatternId);
|
||||
|
||||
if (layerIds.length === 0) {
|
||||
// The field we're suggesting on does not match any existing layer. This will always add
|
||||
// a new layer if possible, but that might not be desirable if the layers are too complicated
|
||||
// already
|
||||
return getEmptyLayerSuggestionsForField(state, generateId(), indexPatternId, field);
|
||||
// The field we're suggesting on does not match any existing layer.
|
||||
// This generates a set of suggestions where we add a layer.
|
||||
// A second set of suggestions is generated for visualizations that don't work with layers
|
||||
const newId = generateId();
|
||||
return getEmptyLayerSuggestionsForField(state, newId, indexPatternId, field).concat(
|
||||
getEmptyLayerSuggestionsForField({ ...state, layers: {} }, newId, indexPatternId, field)
|
||||
);
|
||||
} else {
|
||||
// The field we're suggesting on matches an existing layer. In this case we find the layer with
|
||||
// the fewest configured columns and try to add the field to this table. If this layer does not
|
||||
|
@ -166,7 +171,7 @@ function addFieldAsMetricOperation(
|
|||
layerId: string,
|
||||
indexPattern: IndexPattern,
|
||||
field: IndexPatternField
|
||||
) {
|
||||
): IndexPatternLayer | undefined {
|
||||
const operations = getOperationTypesForField(field);
|
||||
const operationsAlreadyAppliedToThisField = Object.values(layer.columns)
|
||||
.filter(column => hasField(column) && column.sourceField === field.name)
|
||||
|
@ -176,7 +181,7 @@ function addFieldAsMetricOperation(
|
|||
);
|
||||
|
||||
if (!operationCandidate) {
|
||||
return undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
const newColumn = buildColumn({
|
||||
|
@ -206,7 +211,7 @@ function addFieldAsBucketOperation(
|
|||
layerId: string,
|
||||
indexPattern: IndexPattern,
|
||||
field: IndexPatternField
|
||||
) {
|
||||
): IndexPatternLayer {
|
||||
const applicableBucketOperation = getBucketOperation(field);
|
||||
const newColumn = buildColumn({
|
||||
op: applicableBucketOperation,
|
||||
|
@ -250,7 +255,7 @@ function getEmptyLayerSuggestionsForField(
|
|||
layerId: string,
|
||||
indexPatternId: string,
|
||||
field: IndexPatternField
|
||||
) {
|
||||
): IndexPatternSugestion[] {
|
||||
const indexPattern = state.indexPatterns[indexPatternId];
|
||||
let newLayer: IndexPatternLayer | undefined;
|
||||
if (getBucketOperation(field)) {
|
||||
|
@ -279,7 +284,7 @@ function createNewLayerWithBucketAggregation(
|
|||
layerId: string,
|
||||
indexPattern: IndexPattern,
|
||||
field: IndexPatternField
|
||||
) {
|
||||
): IndexPatternLayer {
|
||||
const countColumn = buildColumn({
|
||||
op: 'count',
|
||||
columns: {},
|
||||
|
@ -318,7 +323,7 @@ function createNewLayerWithMetricAggregation(
|
|||
layerId: string,
|
||||
indexPattern: IndexPattern,
|
||||
field: IndexPatternField
|
||||
) {
|
||||
): IndexPatternLayer {
|
||||
const dateField = indexPattern.fields.find(f => f.name === indexPattern.timeFieldName)!;
|
||||
|
||||
const operations = getOperationTypesForField(field);
|
||||
|
@ -356,10 +361,50 @@ function createNewLayerWithMetricAggregation(
|
|||
export function getDatasourceSuggestionsFromCurrentState(
|
||||
state: IndexPatternPrivateState
|
||||
): Array<DatasourceSuggestion<IndexPatternPrivateState>> {
|
||||
const layers = Object.entries(state.layers || {});
|
||||
if (layers.length > 1) {
|
||||
// Return suggestions that reduce the data to each layer individually
|
||||
return layers
|
||||
.map(([layerId, layer], index) => {
|
||||
const hasMatchingLayer = layers.some(
|
||||
([otherLayerId, otherLayer]) =>
|
||||
otherLayerId !== layerId && otherLayer.indexPatternId === layer.indexPatternId
|
||||
);
|
||||
|
||||
const suggestionTitle = hasMatchingLayer
|
||||
? i18n.translate('xpack.lens.indexPatternSuggestion.removeLayerPositionLabel', {
|
||||
defaultMessage: 'Show only layer {layerNumber}',
|
||||
values: { layerNumber: index + 1 },
|
||||
})
|
||||
: i18n.translate('xpack.lens.indexPatternSuggestion.removeLayerLabel', {
|
||||
defaultMessage: 'Show only {indexPatternTitle}',
|
||||
values: { indexPatternTitle: state.indexPatterns[layer.indexPatternId].title },
|
||||
});
|
||||
|
||||
return buildSuggestion({
|
||||
state: {
|
||||
...state,
|
||||
layers: {
|
||||
[layerId]: layer,
|
||||
},
|
||||
},
|
||||
layerId,
|
||||
changeType: 'layers',
|
||||
label: suggestionTitle,
|
||||
});
|
||||
})
|
||||
.concat([
|
||||
buildSuggestion({
|
||||
state,
|
||||
layerId: layers[0][0],
|
||||
changeType: 'unchanged',
|
||||
}),
|
||||
]);
|
||||
}
|
||||
return _.flatten(
|
||||
Object.entries(state.layers || {})
|
||||
.filter(([_id, layer]) => layer.columnOrder.length)
|
||||
.map(([layerId, layer], index) => {
|
||||
.filter(([_id, layer]) => layer.columnOrder.length && layer.indexPatternId)
|
||||
.map(([layerId, layer]) => {
|
||||
const indexPattern = state.indexPatterns[layer.indexPatternId];
|
||||
const [buckets, metrics] = separateBucketColumns(layer);
|
||||
const timeDimension = layer.columnOrder.find(
|
||||
|
@ -432,7 +477,6 @@ function createMetricSuggestion(
|
|||
state: IndexPatternPrivateState,
|
||||
field: IndexPatternField
|
||||
) {
|
||||
const layer = state.layers[layerId];
|
||||
const operationDefinitionsMap = _.indexBy(operationDefinitions, 'type');
|
||||
const [column] = getOperationTypesForField(field)
|
||||
.map(type =>
|
||||
|
@ -457,7 +501,7 @@ function createMetricSuggestion(
|
|||
state,
|
||||
changeType: 'initial',
|
||||
updatedLayer: {
|
||||
...layer,
|
||||
indexPatternId: indexPattern.id,
|
||||
columns: {
|
||||
[newId]:
|
||||
column.dataType !== 'document'
|
||||
|
@ -515,7 +559,7 @@ function createAlternativeMetricSuggestions(
|
|||
suggestedPriority: undefined,
|
||||
});
|
||||
const updatedLayer = {
|
||||
...layer,
|
||||
indexPatternId: indexPattern.id,
|
||||
columns: { [newId]: newColumn },
|
||||
columnOrder: [newId],
|
||||
};
|
||||
|
@ -549,7 +593,7 @@ function createSuggestionWithDefaultDateHistogram(
|
|||
suggestedPriority: undefined,
|
||||
});
|
||||
const updatedLayer = {
|
||||
...layer,
|
||||
indexPatternId: layer.indexPatternId,
|
||||
columns: { ...layer.columns, [newId]: timeColumn },
|
||||
columnOrder: [...buckets, newId, ...metrics],
|
||||
};
|
||||
|
|
|
@ -172,6 +172,7 @@ export async function changeLayerIndexPattern({
|
|||
state,
|
||||
setState,
|
||||
onError,
|
||||
replaceIfPossible,
|
||||
}: {
|
||||
indexPatternId: string;
|
||||
layerId: string;
|
||||
|
@ -179,6 +180,7 @@ export async function changeLayerIndexPattern({
|
|||
state: IndexPatternPrivateState;
|
||||
setState: SetState;
|
||||
onError: ErrorHandler;
|
||||
replaceIfPossible?: boolean;
|
||||
}) {
|
||||
try {
|
||||
const indexPatterns = await loadIndexPatterns({
|
||||
|
@ -197,6 +199,7 @@ export async function changeLayerIndexPattern({
|
|||
...s.indexPatterns,
|
||||
[indexPatternId]: indexPatterns[indexPatternId],
|
||||
},
|
||||
currentIndexPatternId: replaceIfPossible ? indexPatternId : s.currentIndexPatternId,
|
||||
}));
|
||||
} catch (err) {
|
||||
onError(err);
|
||||
|
@ -212,10 +215,14 @@ async function loadIndexPatternRefs(
|
|||
perPage: 10000,
|
||||
});
|
||||
|
||||
return result.savedObjects.map(o => ({
|
||||
id: String(o.id),
|
||||
title: (o.attributes as { title: string }).title,
|
||||
}));
|
||||
return result.savedObjects
|
||||
.map(o => ({
|
||||
id: String(o.id),
|
||||
title: (o.attributes as { title: string }).title,
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
return a.title.localeCompare(b.title);
|
||||
});
|
||||
}
|
||||
|
||||
export async function syncExistingFields({
|
||||
|
|
|
@ -80,7 +80,9 @@ describe('metric_suggestions', () => {
|
|||
layerId: 'l1',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
] as TableSuggestion[]).map(table => expect(getSuggestions({ table })).toEqual([]))
|
||||
] as TableSuggestion[]).map(table =>
|
||||
expect(getSuggestions({ table, keptLayerIds: ['l1'] })).toEqual([])
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -92,6 +94,7 @@ describe('metric_suggestions', () => {
|
|||
layerId: 'l1',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(rest).toHaveLength(0);
|
||||
|
@ -107,4 +110,32 @@ describe('metric_suggestions', () => {
|
|||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('does not suggest for multiple layers', () => {
|
||||
const suggestions = getSuggestions({
|
||||
table: {
|
||||
columns: [numCol('bytes')],
|
||||
isMultiRow: false,
|
||||
layerId: 'l1',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: ['l1', 'l2'],
|
||||
});
|
||||
|
||||
expect(suggestions).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('does not suggest when the suggestion keeps a different layer', () => {
|
||||
const suggestions = getSuggestions({
|
||||
table: {
|
||||
columns: [numCol('bytes')],
|
||||
isMultiRow: false,
|
||||
layerId: 'newer',
|
||||
changeType: 'initial',
|
||||
},
|
||||
keptLayerIds: ['older'],
|
||||
});
|
||||
|
||||
expect(suggestions).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,11 +16,14 @@ import chartMetricSVG from '../assets/chart_metric.svg';
|
|||
export function getSuggestions({
|
||||
table,
|
||||
state,
|
||||
keptLayerIds,
|
||||
}: SuggestionRequest<State>): Array<VisualizationSuggestion<State>> {
|
||||
// We only render metric charts for single-row queries. We require a single, numeric column.
|
||||
if (
|
||||
table.isMultiRow ||
|
||||
table.columns.length > 1 ||
|
||||
keptLayerIds.length > 1 ||
|
||||
(keptLayerIds.length && table.layerId !== keptLayerIds[0]) ||
|
||||
table.columns.length !== 1 ||
|
||||
table.columns[0].operation.dataType !== 'number'
|
||||
) {
|
||||
return [];
|
||||
|
|
|
@ -104,12 +104,14 @@ export interface TableSuggestion {
|
|||
* * `unchanged` means the table is the same in the currently active configuration
|
||||
* * `reduced` means the table is a reduced version of the currently active table (some columns dropped, but not all of them)
|
||||
* * `extended` means the table is an extended version of the currently active table (added one or multiple additional columns)
|
||||
* * `layers` means the change is a change to the layer structure, not to the table
|
||||
*/
|
||||
export type TableChangeType = 'initial' | 'unchanged' | 'reduced' | 'extended';
|
||||
export type TableChangeType = 'initial' | 'unchanged' | 'reduced' | 'extended' | 'layers';
|
||||
|
||||
export interface DatasourceSuggestion<T = unknown> {
|
||||
state: T;
|
||||
table: TableSuggestion;
|
||||
keptLayerIds: string[];
|
||||
}
|
||||
|
||||
export interface DatasourceMetaData {
|
||||
|
@ -262,6 +264,10 @@ export interface SuggestionRequest<T = unknown> {
|
|||
* State is only passed if the visualization is active.
|
||||
*/
|
||||
state?: T;
|
||||
/**
|
||||
* The visualization needs to know which table is being suggested
|
||||
*/
|
||||
keptLayerIds: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -100,7 +100,9 @@ describe('xy_suggestions', () => {
|
|||
layerId: 'first',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
] as TableSuggestion[]).map(table => expect(getSuggestions({ table })).toEqual([]))
|
||||
] as TableSuggestion[]).map(table =>
|
||||
expect(getSuggestions({ table, keptLayerIds: [] })).toEqual([])
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -113,6 +115,7 @@ describe('xy_suggestions', () => {
|
|||
layerId: 'first',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(rest).toHaveLength(0);
|
||||
|
@ -144,6 +147,7 @@ describe('xy_suggestions', () => {
|
|||
layerId: 'first',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(suggestions).toHaveLength(0);
|
||||
|
@ -157,6 +161,7 @@ describe('xy_suggestions', () => {
|
|||
layerId: 'first',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(rest).toHaveLength(0);
|
||||
|
@ -184,6 +189,7 @@ describe('xy_suggestions', () => {
|
|||
changeType: 'unchanged',
|
||||
label: 'Datasource title',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(rest).toHaveLength(0);
|
||||
|
@ -211,6 +217,7 @@ describe('xy_suggestions', () => {
|
|||
},
|
||||
],
|
||||
},
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(rest).toHaveLength(0);
|
||||
|
@ -225,6 +232,7 @@ describe('xy_suggestions', () => {
|
|||
layerId: 'first',
|
||||
changeType: 'reduced',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(rest).toHaveLength(0);
|
||||
|
@ -254,6 +262,7 @@ describe('xy_suggestions', () => {
|
|||
changeType: 'unchanged',
|
||||
},
|
||||
state: currentState,
|
||||
keptLayerIds: ['first'],
|
||||
});
|
||||
|
||||
expect(suggestions).toHaveLength(1);
|
||||
|
@ -288,6 +297,7 @@ describe('xy_suggestions', () => {
|
|||
changeType: 'unchanged',
|
||||
},
|
||||
state: currentState,
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(rest).toHaveLength(0);
|
||||
|
@ -328,6 +338,7 @@ describe('xy_suggestions', () => {
|
|||
changeType: 'unchanged',
|
||||
},
|
||||
state: currentState,
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(rest).toHaveLength(0);
|
||||
|
@ -358,6 +369,7 @@ describe('xy_suggestions', () => {
|
|||
changeType: 'unchanged',
|
||||
},
|
||||
state: currentState,
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
const suggestion = suggestions[suggestions.length - 1];
|
||||
|
@ -392,6 +404,7 @@ describe('xy_suggestions', () => {
|
|||
changeType: 'extended',
|
||||
},
|
||||
state: currentState,
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(rest).toHaveLength(0);
|
||||
|
@ -430,6 +443,7 @@ describe('xy_suggestions', () => {
|
|||
changeType: 'extended',
|
||||
},
|
||||
state: currentState,
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(rest).toHaveLength(0);
|
||||
|
@ -454,6 +468,7 @@ describe('xy_suggestions', () => {
|
|||
layerId: 'first',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(suggestionSubset(suggestion)).toMatchInlineSnapshot(`
|
||||
|
@ -490,6 +505,7 @@ describe('xy_suggestions', () => {
|
|||
layerId: 'first',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(suggestionSubset(suggestion)).toMatchInlineSnapshot(`
|
||||
|
@ -525,6 +541,7 @@ describe('xy_suggestions', () => {
|
|||
layerId: 'first',
|
||||
changeType: 'unchanged',
|
||||
},
|
||||
keptLayerIds: [],
|
||||
});
|
||||
|
||||
expect(suggestionSubset(suggestion)).toMatchInlineSnapshot(`
|
||||
|
|
|
@ -35,6 +35,7 @@ const columnSortOrder = {
|
|||
export function getSuggestions({
|
||||
table,
|
||||
state,
|
||||
keptLayerIds,
|
||||
}: SuggestionRequest<State>): Array<VisualizationSuggestion<State>> {
|
||||
if (
|
||||
// We only render line charts for multi-row queries. We require at least
|
||||
|
@ -48,7 +49,7 @@ export function getSuggestions({
|
|||
return [];
|
||||
}
|
||||
|
||||
const suggestions = getSuggestionForColumns(table, state);
|
||||
const suggestions = getSuggestionForColumns(table, keptLayerIds, state);
|
||||
|
||||
if (suggestions && suggestions instanceof Array) {
|
||||
return suggestions;
|
||||
|
@ -59,32 +60,35 @@ export function getSuggestions({
|
|||
|
||||
function getSuggestionForColumns(
|
||||
table: TableSuggestion,
|
||||
keptLayerIds: string[],
|
||||
currentState?: State
|
||||
): VisualizationSuggestion<State> | Array<VisualizationSuggestion<State>> | undefined {
|
||||
const [buckets, values] = partition(table.columns, col => col.operation.isBucketed);
|
||||
|
||||
if (buckets.length === 1 || buckets.length === 2) {
|
||||
const [x, splitBy] = getBucketMappings(table, currentState);
|
||||
return getSuggestionsForLayer(
|
||||
table.layerId,
|
||||
table.changeType,
|
||||
x,
|
||||
values,
|
||||
return getSuggestionsForLayer({
|
||||
layerId: table.layerId,
|
||||
changeType: table.changeType,
|
||||
xValue: x,
|
||||
yValues: values,
|
||||
splitBy,
|
||||
currentState,
|
||||
table.label
|
||||
);
|
||||
tableLabel: table.label,
|
||||
keptLayerIds,
|
||||
});
|
||||
} else if (buckets.length === 0) {
|
||||
const [x, ...yValues] = prioritizeColumns(values);
|
||||
return getSuggestionsForLayer(
|
||||
table.layerId,
|
||||
table.changeType,
|
||||
x,
|
||||
return getSuggestionsForLayer({
|
||||
layerId: table.layerId,
|
||||
changeType: table.changeType,
|
||||
xValue: x,
|
||||
yValues,
|
||||
undefined,
|
||||
splitBy: undefined,
|
||||
currentState,
|
||||
table.label
|
||||
);
|
||||
tableLabel: table.label,
|
||||
keptLayerIds,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,15 +142,25 @@ function prioritizeColumns(columns: TableSuggestionColumn[]) {
|
|||
);
|
||||
}
|
||||
|
||||
function getSuggestionsForLayer(
|
||||
layerId: string,
|
||||
changeType: TableChangeType,
|
||||
xValue: TableSuggestionColumn,
|
||||
yValues: TableSuggestionColumn[],
|
||||
splitBy?: TableSuggestionColumn,
|
||||
currentState?: State,
|
||||
tableLabel?: string
|
||||
): VisualizationSuggestion<State> | Array<VisualizationSuggestion<State>> {
|
||||
function getSuggestionsForLayer({
|
||||
layerId,
|
||||
changeType,
|
||||
xValue,
|
||||
yValues,
|
||||
splitBy,
|
||||
currentState,
|
||||
tableLabel,
|
||||
keptLayerIds,
|
||||
}: {
|
||||
layerId: string;
|
||||
changeType: TableChangeType;
|
||||
xValue: TableSuggestionColumn;
|
||||
yValues: TableSuggestionColumn[];
|
||||
splitBy?: TableSuggestionColumn;
|
||||
currentState?: State;
|
||||
tableLabel?: string;
|
||||
keptLayerIds: string[];
|
||||
}): VisualizationSuggestion<State> | Array<VisualizationSuggestion<State>> {
|
||||
const title = getSuggestionTitle(yValues, xValue, tableLabel);
|
||||
const seriesType: SeriesType = getSeriesType(currentState, layerId, xValue, changeType);
|
||||
|
||||
|
@ -159,6 +173,7 @@ function getSuggestionsForLayer(
|
|||
splitBy,
|
||||
changeType,
|
||||
xValue,
|
||||
keptLayerIds,
|
||||
};
|
||||
|
||||
const isSameState = currentState && changeType === 'unchanged';
|
||||
|
@ -324,6 +339,7 @@ function buildSuggestion({
|
|||
splitBy,
|
||||
changeType,
|
||||
xValue,
|
||||
keptLayerIds,
|
||||
}: {
|
||||
currentState: XYState | undefined;
|
||||
seriesType: SeriesType;
|
||||
|
@ -333,6 +349,7 @@ function buildSuggestion({
|
|||
splitBy: TableSuggestionColumn | undefined;
|
||||
layerId: string;
|
||||
changeType: TableChangeType;
|
||||
keptLayerIds: string[];
|
||||
}) {
|
||||
const newLayer = {
|
||||
...(getExistingLayer(currentState, layerId) || {}),
|
||||
|
@ -343,13 +360,16 @@ function buildSuggestion({
|
|||
accessors: yValues.map(col => col.columnId),
|
||||
};
|
||||
|
||||
const keptLayers = currentState
|
||||
? currentState.layers.filter(
|
||||
layer => layer.layerId !== layerId && keptLayerIds.includes(layer.layerId)
|
||||
)
|
||||
: [];
|
||||
|
||||
const state: State = {
|
||||
legend: currentState ? currentState.legend : { isVisible: true, position: Position.Right },
|
||||
preferredSeriesType: seriesType,
|
||||
layers: [
|
||||
...(currentState ? currentState.layers.filter(layer => layer.layerId !== layerId) : []),
|
||||
newLayer,
|
||||
],
|
||||
layers: [...keptLayers, newLayer],
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue