mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Text based] Configure Lens suggestion on the fly from Discover (#159559)
## Summary
Part of https://github.com/elastic/kibana/issues/158802
This PR removes the navigation from Discover to Lens and renders a push
flyout instead.

Next tasks (follow-up PRs):
- [ ] Remove the text based support from Lens dataview picker. The FTs
should be removed from there and possibly moved to discover FTs
- [ ] Apply the same flyout in dashboard for text based panels
- [ ] Allow drag and drop between dimensions
- [ ] Investigate why the Field select doesnt close when you click
outside the dropdown
### Checklist
- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Andrea Del Rio <delrio.andre@gmail.com>
This commit is contained in:
parent
90b3e712cb
commit
e8b2303875
43 changed files with 1477 additions and 92 deletions
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { Datatable } from '@kbn/expressions-plugin/common';
|
||||
|
||||
export const lensTablesAdapterMock: Record<string, Datatable> = {
|
||||
default: {
|
||||
columns: [
|
||||
{
|
||||
id: 'col-0-1',
|
||||
meta: {
|
||||
dimensionName: 'Slice size',
|
||||
type: 'number',
|
||||
},
|
||||
name: 'Field 1',
|
||||
},
|
||||
{
|
||||
id: 'col-0-2',
|
||||
meta: {
|
||||
dimensionName: 'Slice',
|
||||
type: 'number',
|
||||
},
|
||||
name: 'Field 2',
|
||||
},
|
||||
],
|
||||
rows: [
|
||||
{
|
||||
'col-0-1': 0,
|
||||
'col-0-2': 0,
|
||||
'col-0-3': 0,
|
||||
'col-0-4': 0,
|
||||
},
|
||||
],
|
||||
type: 'datatable',
|
||||
},
|
||||
};
|
|
@ -5,7 +5,7 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||
import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks';
|
||||
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
|
||||
|
@ -33,6 +33,7 @@ export const unifiedHistogramServicesMock = {
|
|||
suggestions: jest.fn(() => allSuggestionsMock),
|
||||
};
|
||||
}),
|
||||
EditLensConfigPanelApi: jest.fn().mockResolvedValue(<span>Lens Config Panel Component</span>),
|
||||
},
|
||||
storage: {
|
||||
get: jest.fn(),
|
|
@ -233,6 +233,17 @@ describe('Chart', () => {
|
|||
expect(component.find(SuggestionSelector).exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render the edit on the fly button when chart is visible and suggestions exist', async () => {
|
||||
const component = await mountComponent({
|
||||
currentSuggestion: currentSuggestionMock,
|
||||
allSuggestions: allSuggestionsMock,
|
||||
isPlainRecord: true,
|
||||
});
|
||||
expect(
|
||||
component.find('[data-test-subj="unifiedHistogramEditFlyoutVisualization"]').exists()
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render the save button when chart is visible and suggestions exist', async () => {
|
||||
const component = await mountComponent({
|
||||
currentSuggestion: currentSuggestionMock,
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ReactElement, useMemo, useState } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import React, { ReactElement, useMemo, useState, useEffect, useCallback, memo } from 'react';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiContextMenu,
|
||||
|
@ -18,6 +17,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { Suggestion } from '@kbn/lens-plugin/public';
|
||||
import type { Datatable } from '@kbn/expressions-plugin/common';
|
||||
import { DataView, DataViewField, DataViewType } from '@kbn/data-views-plugin/public';
|
||||
import type { LensEmbeddableInput } from '@kbn/lens-plugin/public';
|
||||
import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
|
@ -42,6 +42,7 @@ import { useTotalHits } from './hooks/use_total_hits';
|
|||
import { useRequestParams } from './hooks/use_request_params';
|
||||
import { useChartStyles } from './hooks/use_chart_styles';
|
||||
import { useChartActions } from './hooks/use_chart_actions';
|
||||
import { useChartConfigPanel } from './hooks/use_chart_config_panel';
|
||||
import { getLensAttributes } from './utils/get_lens_attributes';
|
||||
import { useRefetch } from './hooks/use_refetch';
|
||||
import { useEditVisualization } from './hooks/use_edit_visualization';
|
||||
|
@ -67,6 +68,7 @@ export interface ChartProps {
|
|||
disableTriggers?: LensEmbeddableInput['disableTriggers'];
|
||||
disabledActions?: LensEmbeddableInput['disabledActions'];
|
||||
input$?: UnifiedHistogramInput$;
|
||||
lensTablesAdapter?: Record<string, Datatable>;
|
||||
onResetChartHeight?: () => void;
|
||||
onChartHiddenChange?: (chartHidden: boolean) => void;
|
||||
onTimeIntervalChange?: (timeInterval: string) => void;
|
||||
|
@ -101,6 +103,7 @@ export function Chart({
|
|||
disableTriggers,
|
||||
disabledActions,
|
||||
input$: originalInput$,
|
||||
lensTablesAdapter,
|
||||
onResetChartHeight,
|
||||
onChartHiddenChange,
|
||||
onTimeIntervalChange,
|
||||
|
@ -112,6 +115,7 @@ export function Chart({
|
|||
onBrushEnd,
|
||||
}: ChartProps) {
|
||||
const [isSaveModalVisible, setIsSaveModalVisible] = useState(false);
|
||||
const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
|
||||
const {
|
||||
showChartOptionsPopover,
|
||||
chartRef,
|
||||
|
@ -190,6 +194,7 @@ export function Chart({
|
|||
histogramCss,
|
||||
breakdownFieldSelectorGroupCss,
|
||||
breakdownFieldSelectorItemCss,
|
||||
suggestionsSelectorItemCss,
|
||||
chartToolButtonCss,
|
||||
} = useChartStyles(chartVisible);
|
||||
|
||||
|
@ -215,6 +220,34 @@ export function Chart({
|
|||
]
|
||||
);
|
||||
|
||||
const ChartConfigPanel = useChartConfigPanel({
|
||||
services,
|
||||
lensAttributesContext,
|
||||
dataView,
|
||||
lensTablesAdapter,
|
||||
currentSuggestion,
|
||||
isFlyoutVisible,
|
||||
setIsFlyoutVisible,
|
||||
isPlainRecord,
|
||||
query: originalQuery,
|
||||
onSuggestionChange,
|
||||
});
|
||||
|
||||
const onSuggestionSelectorChange = useCallback(
|
||||
(s: Suggestion | undefined) => {
|
||||
onSuggestionChange?.(s);
|
||||
},
|
||||
[onSuggestionChange]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// close the flyout for dataview mode
|
||||
// or if no chart is visible
|
||||
if (!chartVisible && isFlyoutVisible) {
|
||||
setIsFlyoutVisible(false);
|
||||
}
|
||||
}, [chartVisible, isFlyoutVisible]);
|
||||
|
||||
const onEditVisualization = useEditVisualization({
|
||||
services,
|
||||
dataView,
|
||||
|
@ -226,6 +259,24 @@ export function Chart({
|
|||
const canSaveVisualization =
|
||||
chartVisible && currentSuggestion && services.capabilities.dashboard?.showWriteControls;
|
||||
|
||||
const renderEditButton = useMemo(
|
||||
() => (
|
||||
<EuiButtonIcon
|
||||
size="xs"
|
||||
iconType="pencil"
|
||||
onClick={() => setIsFlyoutVisible(true)}
|
||||
data-test-subj="unifiedHistogramEditFlyoutVisualization"
|
||||
aria-label={i18n.translate('unifiedHistogram.editVisualizationButton', {
|
||||
defaultMessage: 'Edit visualization',
|
||||
})}
|
||||
disabled={isFlyoutVisible}
|
||||
/>
|
||||
),
|
||||
[isFlyoutVisible]
|
||||
);
|
||||
|
||||
const canEditVisualizationOnTheFly = isPlainRecord && chartVisible;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
className={className}
|
||||
|
@ -237,6 +288,7 @@ export function Chart({
|
|||
<EuiFlexItem grow={false} css={resultCountCss}>
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceBetween"
|
||||
alignItems="center"
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
css={resultCountInnerCss}
|
||||
|
@ -267,11 +319,11 @@ export function Chart({
|
|||
</EuiFlexItem>
|
||||
)}
|
||||
{chartVisible && currentSuggestion && allSuggestions && allSuggestions?.length > 1 && (
|
||||
<EuiFlexItem css={breakdownFieldSelectorItemCss}>
|
||||
<EuiFlexItem css={suggestionsSelectorItemCss}>
|
||||
<SuggestionSelector
|
||||
suggestions={allSuggestions}
|
||||
activeSuggestion={currentSuggestion}
|
||||
onSuggestionChange={onSuggestionChange}
|
||||
onSuggestionChange={onSuggestionSelectorChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
@ -296,6 +348,21 @@ export function Chart({
|
|||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
{canEditVisualizationOnTheFly && (
|
||||
<EuiFlexItem grow={false} css={chartToolButtonCss}>
|
||||
{!isFlyoutVisible ? (
|
||||
<EuiToolTip
|
||||
content={i18n.translate('unifiedHistogram.editVisualizationButton', {
|
||||
defaultMessage: 'Edit visualization',
|
||||
})}
|
||||
>
|
||||
{renderEditButton}
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
renderEditButton
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{onEditVisualization && (
|
||||
<EuiFlexItem grow={false} css={chartToolButtonCss}>
|
||||
<EuiToolTip
|
||||
|
@ -387,6 +454,7 @@ export function Chart({
|
|||
isSaveable={false}
|
||||
/>
|
||||
)}
|
||||
{isFlyoutVisible && ChartConfigPanel}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -146,6 +146,7 @@ export function Histogram({
|
|||
const chartCss = css`
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
margin-block: ${euiTheme.size.xs};
|
||||
|
||||
& > div {
|
||||
height: 100%;
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { act } from 'react-test-renderer';
|
||||
import { setTimeout } from 'timers/promises';
|
||||
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
|
||||
import { unifiedHistogramServicesMock } from '../../__mocks__/services';
|
||||
import { lensTablesAdapterMock } from '../../__mocks__/lens_table_adapter';
|
||||
import { useChartConfigPanel } from './use_chart_config_panel';
|
||||
import type { LensAttributesContext } from '../utils/get_lens_attributes';
|
||||
|
||||
describe('useChartConfigPanel', () => {
|
||||
it('should return a jsx element to edit the visualization', async () => {
|
||||
const lensAttributes = {
|
||||
visualizationType: 'lnsXY',
|
||||
title: 'test',
|
||||
} as TypedLensByValueInput['attributes'];
|
||||
const hook = renderHook(() =>
|
||||
useChartConfigPanel({
|
||||
services: unifiedHistogramServicesMock,
|
||||
dataView: dataViewWithTimefieldMock,
|
||||
lensAttributesContext: {
|
||||
attributes: lensAttributes,
|
||||
} as unknown as LensAttributesContext,
|
||||
isFlyoutVisible: true,
|
||||
setIsFlyoutVisible: jest.fn(),
|
||||
isPlainRecord: true,
|
||||
lensTablesAdapter: lensTablesAdapterMock,
|
||||
query: {
|
||||
sql: 'Select * from test',
|
||||
},
|
||||
})
|
||||
);
|
||||
await act(() => setTimeout(0));
|
||||
expect(hook.result.current).toBeDefined();
|
||||
expect(hook.result.current).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should return null if not in text based mode', async () => {
|
||||
const lensAttributes = {
|
||||
visualizationType: 'lnsXY',
|
||||
title: 'test',
|
||||
} as TypedLensByValueInput['attributes'];
|
||||
const hook = renderHook(() =>
|
||||
useChartConfigPanel({
|
||||
services: unifiedHistogramServicesMock,
|
||||
dataView: dataViewWithTimefieldMock,
|
||||
lensAttributesContext: {
|
||||
attributes: lensAttributes,
|
||||
} as unknown as LensAttributesContext,
|
||||
isFlyoutVisible: true,
|
||||
setIsFlyoutVisible: jest.fn(),
|
||||
isPlainRecord: false,
|
||||
})
|
||||
);
|
||||
await act(() => setTimeout(0));
|
||||
expect(hook.result.current).toBeNull();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { AggregateQuery, Query } from '@kbn/es-query';
|
||||
import { isEqual } from 'lodash';
|
||||
import type { Suggestion } from '@kbn/lens-plugin/public';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import type { Datatable } from '@kbn/expressions-plugin/common';
|
||||
|
||||
import type { UnifiedHistogramServices } from '../../types';
|
||||
import type { LensAttributesContext } from '../utils/get_lens_attributes';
|
||||
|
||||
export function useChartConfigPanel({
|
||||
services,
|
||||
lensAttributesContext,
|
||||
dataView,
|
||||
lensTablesAdapter,
|
||||
currentSuggestion,
|
||||
isFlyoutVisible,
|
||||
setIsFlyoutVisible,
|
||||
isPlainRecord,
|
||||
query,
|
||||
onSuggestionChange,
|
||||
}: {
|
||||
services: UnifiedHistogramServices;
|
||||
lensAttributesContext: LensAttributesContext;
|
||||
dataView: DataView;
|
||||
isFlyoutVisible: boolean;
|
||||
setIsFlyoutVisible: (flag: boolean) => void;
|
||||
lensTablesAdapter?: Record<string, Datatable>;
|
||||
currentSuggestion?: Suggestion;
|
||||
isPlainRecord?: boolean;
|
||||
query?: Query | AggregateQuery;
|
||||
onSuggestionChange?: (suggestion: Suggestion | undefined) => void;
|
||||
}) {
|
||||
const [editLensConfigPanel, setEditLensConfigPanel] = useState<JSX.Element | null>(null);
|
||||
const previousSuggestion = useRef<Suggestion | undefined>(undefined);
|
||||
const previousAdapters = useRef<Record<string, Datatable> | undefined>(undefined);
|
||||
const previousQuery = useRef<Query | AggregateQuery | undefined>(undefined);
|
||||
const updateSuggestion = useCallback(
|
||||
(datasourceState, visualizationState) => {
|
||||
const updatedSuggestion = {
|
||||
...currentSuggestion,
|
||||
...(datasourceState && { datasourceState }),
|
||||
...(visualizationState && { visualizationState }),
|
||||
} as Suggestion;
|
||||
onSuggestionChange?.(updatedSuggestion);
|
||||
},
|
||||
[currentSuggestion, onSuggestionChange]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const dataHasChanged =
|
||||
Boolean(lensTablesAdapter) &&
|
||||
!isEqual(previousAdapters.current, lensTablesAdapter) &&
|
||||
query !== previousQuery?.current;
|
||||
async function fetchLensConfigComponent() {
|
||||
const Component = await services.lens.EditLensConfigPanelApi();
|
||||
const panel = (
|
||||
<Component
|
||||
attributes={lensAttributesContext.attributes}
|
||||
dataView={dataView}
|
||||
adaptersTables={lensTablesAdapter}
|
||||
updateAll={updateSuggestion}
|
||||
setIsFlyoutVisible={setIsFlyoutVisible}
|
||||
datasourceId="textBased"
|
||||
/>
|
||||
);
|
||||
setEditLensConfigPanel(panel);
|
||||
previousSuggestion.current = currentSuggestion;
|
||||
previousAdapters.current = lensTablesAdapter;
|
||||
if (dataHasChanged) {
|
||||
previousQuery.current = query;
|
||||
}
|
||||
}
|
||||
const suggestionHasChanged = currentSuggestion?.title !== previousSuggestion?.current?.title;
|
||||
// rerender the component if the data has changed or the suggestion
|
||||
// as I can have different suggestions for the same data
|
||||
if (isPlainRecord && (dataHasChanged || suggestionHasChanged || !isFlyoutVisible)) {
|
||||
fetchLensConfigComponent();
|
||||
}
|
||||
}, [
|
||||
lensAttributesContext.attributes,
|
||||
services.lens,
|
||||
dataView,
|
||||
updateSuggestion,
|
||||
isPlainRecord,
|
||||
currentSuggestion,
|
||||
query,
|
||||
isFlyoutVisible,
|
||||
lensTablesAdapter,
|
||||
setIsFlyoutVisible,
|
||||
]);
|
||||
|
||||
return isPlainRecord ? editLensConfigPanel : null;
|
||||
}
|
|
@ -56,6 +56,11 @@ export const useChartStyles = (chartVisible: boolean) => {
|
|||
align-items: flex-end;
|
||||
padding-left: ${euiTheme.size.s};
|
||||
`;
|
||||
const suggestionsSelectorItemCss = css`
|
||||
min-width: 0;
|
||||
align-items: flex-start;
|
||||
padding-left: ${euiTheme.size.s};
|
||||
`;
|
||||
const chartToolButtonCss = css`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -70,6 +75,7 @@ export const useChartStyles = (chartVisible: boolean) => {
|
|||
histogramCss,
|
||||
breakdownFieldSelectorGroupCss,
|
||||
breakdownFieldSelectorItemCss,
|
||||
suggestionsSelectorItemCss,
|
||||
chartToolButtonCss,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -81,6 +81,21 @@ describe('useEditVisualization', () => {
|
|||
expect(hook.result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined if is on text based mode', async () => {
|
||||
getTriggerCompatibleActions.mockReturnValue(Promise.resolve([{ id: 'test' }]));
|
||||
const hook = renderHook(() =>
|
||||
useEditVisualization({
|
||||
services: unifiedHistogramServicesMock,
|
||||
dataView: dataViewWithTimefieldMock,
|
||||
relativeTimeRange: { from: 'now-15m', to: 'now' },
|
||||
lensAttributes: {} as unknown as TypedLensByValueInput['attributes'],
|
||||
isPlainRecord: true,
|
||||
})
|
||||
);
|
||||
await act(() => setTimeout(0));
|
||||
expect(hook.result.current).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined if the time field is not visualizable', async () => {
|
||||
getTriggerCompatibleActions.mockReturnValue(Promise.resolve([{ id: 'test' }]));
|
||||
const dataView = {
|
||||
|
|
|
@ -32,10 +32,10 @@ export const useEditVisualization = ({
|
|||
const [canVisualize, setCanVisualize] = useState(false);
|
||||
|
||||
const checkCanVisualize = useCallback(async () => {
|
||||
if (!dataView.id) {
|
||||
if (!dataView.id || isPlainRecord) {
|
||||
return false;
|
||||
}
|
||||
if (!isPlainRecord && (!dataView.isTimeBased() || !dataView.getTimeField().visualizable)) {
|
||||
if (!dataView.isTimeBased() || !dataView.getTimeField().visualizable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ export const SuggestionSelector = ({
|
|||
const { euiTheme } = useEuiTheme();
|
||||
const suggestionComboCss = css`
|
||||
width: 100%;
|
||||
max-width: ${euiTheme.base * 22}px;
|
||||
max-width: ${euiTheme.base * 15}px;
|
||||
`;
|
||||
|
||||
return (
|
||||
|
@ -78,9 +78,7 @@ export const SuggestionSelector = ({
|
|||
>
|
||||
<EuiComboBox
|
||||
data-test-subj="unifiedHistogramSuggestionSelector"
|
||||
prepend={i18n.translate('unifiedHistogram.suggestionSelectorLabel', {
|
||||
defaultMessage: 'Visualization',
|
||||
})}
|
||||
prepend={<EuiIcon type={activeSuggestion?.previewIcon ?? 'empty'} />}
|
||||
placeholder={i18n.translate('unifiedHistogram.suggestionSelectorPlaceholder', {
|
||||
defaultMessage: 'Select visualization',
|
||||
})}
|
||||
|
@ -88,9 +86,9 @@ export const SuggestionSelector = ({
|
|||
options={suggestionOptions}
|
||||
selectedOptions={selectedSuggestion}
|
||||
onChange={onSelectionChange}
|
||||
compressed
|
||||
fullWidth={true}
|
||||
isClearable={false}
|
||||
compressed
|
||||
onFocus={disableFieldPopover}
|
||||
onBlur={enableFieldPopover}
|
||||
renderOption={(option) => {
|
||||
|
|
|
@ -15,6 +15,7 @@ import { UnifiedHistogramFetchStatus } from '../../types';
|
|||
import { dataViewMock } from '../../__mocks__/data_view';
|
||||
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
|
||||
import { currentSuggestionMock } from '../../__mocks__/suggestions';
|
||||
import { lensTablesAdapterMock } from '../../__mocks__/lens_table_adapter';
|
||||
import { unifiedHistogramServicesMock } from '../../__mocks__/services';
|
||||
import {
|
||||
createStateService,
|
||||
|
@ -28,6 +29,7 @@ describe('useStateProps', () => {
|
|||
breakdownField: 'bytes',
|
||||
chartHidden: false,
|
||||
lensRequestAdapter: new RequestAdapter(),
|
||||
lensTablesAdapter: lensTablesAdapterMock,
|
||||
timeInterval: 'auto',
|
||||
topPanelHeight: 100,
|
||||
totalHitsStatus: UnifiedHistogramFetchStatus.uninitialized,
|
||||
|
@ -82,6 +84,37 @@ describe('useStateProps', () => {
|
|||
"total": undefined,
|
||||
},
|
||||
"isPlainRecord": false,
|
||||
"lensTablesAdapter": Object {
|
||||
"default": Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice size",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 1",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-2",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 2",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
"col-0-2": 0,
|
||||
"col-0-3": 0,
|
||||
"col-0-4": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
},
|
||||
"onBreakdownFieldChange": [Function],
|
||||
"onChartHiddenChange": [Function],
|
||||
"onChartLoad": [Function],
|
||||
|
@ -126,6 +159,37 @@ describe('useStateProps', () => {
|
|||
"total": undefined,
|
||||
},
|
||||
"isPlainRecord": true,
|
||||
"lensTablesAdapter": Object {
|
||||
"default": Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice size",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 1",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-2",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 2",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
"col-0-2": 0,
|
||||
"col-0-3": 0,
|
||||
"col-0-4": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
},
|
||||
"onBreakdownFieldChange": [Function],
|
||||
"onChartHiddenChange": [Function],
|
||||
"onChartLoad": [Function],
|
||||
|
@ -191,6 +255,37 @@ describe('useStateProps', () => {
|
|||
"total": undefined,
|
||||
},
|
||||
"isPlainRecord": false,
|
||||
"lensTablesAdapter": Object {
|
||||
"default": Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice size",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 1",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-2",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 2",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
"col-0-2": 0,
|
||||
"col-0-3": 0,
|
||||
"col-0-4": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
},
|
||||
"onBreakdownFieldChange": [Function],
|
||||
"onChartHiddenChange": [Function],
|
||||
"onChartLoad": [Function],
|
||||
|
@ -232,6 +327,37 @@ describe('useStateProps', () => {
|
|||
"total": undefined,
|
||||
},
|
||||
"isPlainRecord": false,
|
||||
"lensTablesAdapter": Object {
|
||||
"default": Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice size",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 1",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-2",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 2",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
"col-0-2": 0,
|
||||
"col-0-3": 0,
|
||||
"col-0-4": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
},
|
||||
"onBreakdownFieldChange": [Function],
|
||||
"onChartHiddenChange": [Function],
|
||||
"onChartLoad": [Function],
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
timeIntervalSelector,
|
||||
totalHitsResultSelector,
|
||||
totalHitsStatusSelector,
|
||||
lensTablesAdapterSelector,
|
||||
} from '../utils/state_selectors';
|
||||
import { useStateSelector } from '../utils/use_state_selector';
|
||||
|
||||
|
@ -44,7 +45,7 @@ export const useStateProps = ({
|
|||
const timeInterval = useStateSelector(stateService?.state$, timeIntervalSelector);
|
||||
const totalHitsResult = useStateSelector(stateService?.state$, totalHitsResultSelector);
|
||||
const totalHitsStatus = useStateSelector(stateService?.state$, totalHitsStatusSelector);
|
||||
|
||||
const lensTablesAdapter = useStateSelector(stateService?.state$, lensTablesAdapterSelector);
|
||||
/**
|
||||
* Contexts
|
||||
*/
|
||||
|
@ -139,6 +140,7 @@ export const useStateProps = ({
|
|||
(event: UnifiedHistogramChartLoadEvent) => {
|
||||
// We need to store the Lens request adapter in order to inspect its requests
|
||||
stateService?.setLensRequestAdapter(event.adapters.requests);
|
||||
stateService?.setLensTablesAdapter(event.adapters.tables?.tables);
|
||||
},
|
||||
[stateService]
|
||||
);
|
||||
|
@ -174,6 +176,7 @@ export const useStateProps = ({
|
|||
breakdown,
|
||||
request,
|
||||
isPlainRecord,
|
||||
lensTablesAdapter,
|
||||
onTopPanelHeightChange,
|
||||
onTimeIntervalChange,
|
||||
onTotalHitsChange,
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import { RequestAdapter } from '@kbn/inspector-plugin/common';
|
||||
import { UnifiedHistogramFetchStatus } from '../..';
|
||||
import { unifiedHistogramServicesMock } from '../../__mocks__/services';
|
||||
import { lensTablesAdapterMock } from '../../__mocks__/lens_table_adapter';
|
||||
import {
|
||||
getChartHidden,
|
||||
getTopPanelHeight,
|
||||
|
@ -46,6 +47,7 @@ describe('UnifiedHistogramStateService', () => {
|
|||
breakdownField: 'bytes',
|
||||
chartHidden: false,
|
||||
lensRequestAdapter: new RequestAdapter(),
|
||||
lensTablesAdapter: lensTablesAdapterMock,
|
||||
timeInterval: 'auto',
|
||||
topPanelHeight: 100,
|
||||
totalHitsStatus: UnifiedHistogramFetchStatus.uninitialized,
|
||||
|
@ -134,6 +136,8 @@ describe('UnifiedHistogramStateService', () => {
|
|||
expect(state).toEqual(newState);
|
||||
stateService.setLensRequestAdapter(undefined);
|
||||
newState = { ...newState, lensRequestAdapter: undefined };
|
||||
stateService.setLensTablesAdapter(undefined);
|
||||
newState = { ...newState, lensTablesAdapter: undefined };
|
||||
expect(state).toEqual(newState);
|
||||
stateService.setTotalHits({
|
||||
totalHitsStatus: UnifiedHistogramFetchStatus.complete,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import type { RequestAdapter } from '@kbn/inspector-plugin/common';
|
||||
import type { Suggestion } from '@kbn/lens-plugin/public';
|
||||
import type { Datatable } from '@kbn/expressions-plugin/common';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { UnifiedHistogramFetchStatus } from '../..';
|
||||
import type { UnifiedHistogramServices } from '../../types';
|
||||
|
@ -40,6 +41,10 @@ export interface UnifiedHistogramState {
|
|||
* The current Lens request adapter
|
||||
*/
|
||||
lensRequestAdapter: RequestAdapter | undefined;
|
||||
/**
|
||||
* The current Lens request table
|
||||
*/
|
||||
lensTablesAdapter?: Record<string, Datatable>;
|
||||
/**
|
||||
* The current time interval of the chart
|
||||
*/
|
||||
|
@ -108,6 +113,10 @@ export interface UnifiedHistogramStateService {
|
|||
* Sets the current Lens request adapter
|
||||
*/
|
||||
setLensRequestAdapter: (lensRequestAdapter: RequestAdapter | undefined) => void;
|
||||
/**
|
||||
* Sets the current Lens tables
|
||||
*/
|
||||
setLensTablesAdapter: (lensTablesAdapter: Record<string, Datatable> | undefined) => void;
|
||||
/**
|
||||
* Sets the current total hits status and result
|
||||
*/
|
||||
|
@ -190,6 +199,10 @@ export const createStateService = (
|
|||
updateState({ lensRequestAdapter });
|
||||
},
|
||||
|
||||
setLensTablesAdapter: (lensTablesAdapter: Record<string, Datatable> | undefined) => {
|
||||
updateState({ lensTablesAdapter });
|
||||
},
|
||||
|
||||
setTotalHits: (totalHits: {
|
||||
totalHitsStatus: UnifiedHistogramFetchStatus;
|
||||
totalHitsResult: number | Error | undefined;
|
||||
|
|
|
@ -15,3 +15,4 @@ export const topPanelHeightSelector = (state: UnifiedHistogramState) => state.to
|
|||
export const totalHitsResultSelector = (state: UnifiedHistogramState) => state.totalHitsResult;
|
||||
export const totalHitsStatusSelector = (state: UnifiedHistogramState) => state.totalHitsStatus;
|
||||
export const currentSuggestionSelector = (state: UnifiedHistogramState) => state.currentSuggestion;
|
||||
export const lensTablesAdapterSelector = (state: UnifiedHistogramState) => state.lensTablesAdapter;
|
||||
|
|
|
@ -11,6 +11,7 @@ import { PropsWithChildren, ReactElement, RefObject } from 'react';
|
|||
import React, { useMemo } from 'react';
|
||||
import { createHtmlPortalNode, InPortal, OutPortal } from 'react-reverse-portal';
|
||||
import { css } from '@emotion/css';
|
||||
import type { Datatable } from '@kbn/expressions-plugin/common';
|
||||
import type { DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
||||
import type { LensEmbeddableInput, LensSuggestionsApi, Suggestion } from '@kbn/lens-plugin/public';
|
||||
import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
|
@ -77,6 +78,7 @@ export interface UnifiedHistogramLayoutProps extends PropsWithChildren<unknown>
|
|||
* Context object for the hits count -- leave undefined to hide the hits count
|
||||
*/
|
||||
hits?: UnifiedHistogramHitsContext;
|
||||
lensTablesAdapter?: Record<string, Datatable>;
|
||||
/**
|
||||
* Context object for the chart -- leave undefined to hide the chart
|
||||
*/
|
||||
|
@ -169,6 +171,7 @@ export const UnifiedHistogramLayout = ({
|
|||
columns,
|
||||
request,
|
||||
hits,
|
||||
lensTablesAdapter,
|
||||
chart: originalChart,
|
||||
breakdown,
|
||||
resizeRef,
|
||||
|
@ -273,6 +276,7 @@ export const UnifiedHistogramLayout = ({
|
|||
onChartLoad={onChartLoad}
|
||||
onFilter={onFilter}
|
||||
onBrushEnd={onBrushEnd}
|
||||
lensTablesAdapter={lensTablesAdapter}
|
||||
/>
|
||||
</InPortal>
|
||||
<InPortal node={mainPanelNode}>{children}</InPortal>
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { EuiFlyout, EuiLoadingSpinner, EuiOverlayMask } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Provider } from 'react-redux';
|
||||
import { PreloadedState } from '@reduxjs/toolkit';
|
||||
import { css } from '@emotion/react';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type { LensPluginStartDependencies } from '../../../plugin';
|
||||
import {
|
||||
makeConfigureStore,
|
||||
LensRootStore,
|
||||
LensAppState,
|
||||
LensState,
|
||||
} from '../../../state_management';
|
||||
import { getPreloadedState } from '../../../state_management/lens_slice';
|
||||
|
||||
import type { DatasourceMap, VisualizationMap } from '../../../types';
|
||||
import {
|
||||
LensEditConfigurationFlyout,
|
||||
type EditConfigPanelProps,
|
||||
} from './lens_configuration_flyout';
|
||||
import type { LensAppServices } from '../../types';
|
||||
|
||||
export type EditLensConfigurationProps = Omit<
|
||||
EditConfigPanelProps,
|
||||
'startDependencies' | 'coreStart' | 'visualizationMap' | 'datasourceMap'
|
||||
>;
|
||||
|
||||
function LoadingSpinnerWithOverlay() {
|
||||
return (
|
||||
<EuiOverlayMask>
|
||||
<EuiLoadingSpinner />
|
||||
</EuiOverlayMask>
|
||||
);
|
||||
}
|
||||
|
||||
export function getEditLensConfiguration(
|
||||
coreStart: CoreStart,
|
||||
startDependencies: LensPluginStartDependencies,
|
||||
visualizationMap?: VisualizationMap,
|
||||
datasourceMap?: DatasourceMap
|
||||
) {
|
||||
return ({
|
||||
attributes,
|
||||
dataView,
|
||||
updateAll,
|
||||
setIsFlyoutVisible,
|
||||
datasourceId,
|
||||
adaptersTables,
|
||||
}: EditLensConfigurationProps) => {
|
||||
const [lensServices, setLensServices] = useState<LensAppServices>();
|
||||
useEffect(() => {
|
||||
async function loadLensService() {
|
||||
const { getLensServices, getLensAttributeService } = await import(
|
||||
'../../../async_services'
|
||||
);
|
||||
const lensServicesT = await getLensServices(
|
||||
coreStart,
|
||||
startDependencies,
|
||||
getLensAttributeService(coreStart, startDependencies)
|
||||
);
|
||||
|
||||
setLensServices(lensServicesT);
|
||||
}
|
||||
loadLensService();
|
||||
}, []);
|
||||
|
||||
if (!lensServices || !datasourceMap || !visualizationMap || !dataView.id) {
|
||||
return <LoadingSpinnerWithOverlay />;
|
||||
}
|
||||
const datasourceState = attributes.state.datasourceStates[datasourceId];
|
||||
const storeDeps = {
|
||||
lensServices,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
initialContext:
|
||||
datasourceState && 'initialContext' in datasourceState
|
||||
? datasourceState.initialContext
|
||||
: undefined,
|
||||
};
|
||||
const lensStore: LensRootStore = makeConfigureStore(storeDeps, {
|
||||
lens: getPreloadedState(storeDeps) as LensAppState,
|
||||
} as unknown as PreloadedState<LensState>);
|
||||
const closeFlyout = () => {
|
||||
setIsFlyoutVisible?.(false);
|
||||
};
|
||||
|
||||
const configPanelProps = {
|
||||
attributes,
|
||||
dataView,
|
||||
updateAll,
|
||||
setIsFlyoutVisible,
|
||||
datasourceId,
|
||||
adaptersTables,
|
||||
coreStart,
|
||||
startDependencies,
|
||||
visualizationMap,
|
||||
datasourceMap,
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlyout
|
||||
type="push"
|
||||
ownFocus
|
||||
onClose={closeFlyout}
|
||||
aria-labelledby={i18n.translate('xpack.lens.config.editLabel', {
|
||||
defaultMessage: 'Edit configuration',
|
||||
})}
|
||||
size="s"
|
||||
className="lnsEditConfigurationFlyout"
|
||||
css={css`
|
||||
background: none;
|
||||
`}
|
||||
hideCloseButton
|
||||
>
|
||||
<Provider store={lensStore}>
|
||||
<LensEditConfigurationFlyout {...configPanelProps} />
|
||||
</Provider>
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
||||
}
|
|
@ -0,0 +1,433 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { EuiFlyoutBody } from '@elastic/eui';
|
||||
import { mountWithProvider } from '../../../mocks';
|
||||
import type { Query, AggregateQuery } from '@kbn/es-query';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import {
|
||||
mockVisualizationMap,
|
||||
mockDatasourceMap,
|
||||
mockStoreDeps,
|
||||
mockDataPlugin,
|
||||
} from '../../../mocks';
|
||||
import type { LensPluginStartDependencies } from '../../../plugin';
|
||||
import { createMockStartDependencies } from '../../../editor_frame_service/mocks';
|
||||
import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component';
|
||||
import { VisualizationToolbar } from '../../../editor_frame_service/editor_frame/workspace_panel';
|
||||
import { ConfigPanelWrapper } from '../../../editor_frame_service/editor_frame/config_panel/config_panel';
|
||||
import {
|
||||
LensEditConfigurationFlyout,
|
||||
type EditConfigPanelProps,
|
||||
} from './lens_configuration_flyout';
|
||||
|
||||
let container: HTMLDivElement | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.createElement('div');
|
||||
container.id = 'lensContainer';
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (container && container.parentNode) {
|
||||
container.parentNode.removeChild(container);
|
||||
}
|
||||
|
||||
container = undefined;
|
||||
});
|
||||
|
||||
describe('LensEditConfigurationFlyout', () => {
|
||||
const mockStartDependencies =
|
||||
createMockStartDependencies() as unknown as LensPluginStartDependencies;
|
||||
const data = mockDataPlugin();
|
||||
(data.query.timefilter.timefilter.getTime as jest.Mock).mockReturnValue({
|
||||
from: 'now-2m',
|
||||
to: 'now',
|
||||
});
|
||||
const startDependencies = {
|
||||
...mockStartDependencies,
|
||||
data,
|
||||
};
|
||||
|
||||
function prepareAndMountComponent(
|
||||
props: ReturnType<typeof getDefaultProps>,
|
||||
query?: Query | AggregateQuery
|
||||
) {
|
||||
return mountWithProvider(
|
||||
<LensEditConfigurationFlyout {...props} />,
|
||||
{
|
||||
preloadedState: {
|
||||
datasourceStates: {
|
||||
testDatasource: {
|
||||
isLoading: false,
|
||||
state: 'state',
|
||||
},
|
||||
},
|
||||
activeDatasourceId: 'testDatasource',
|
||||
query: query as Query,
|
||||
},
|
||||
storeDeps: mockStoreDeps({
|
||||
datasourceMap: props.datasourceMap,
|
||||
visualizationMap: props.visualizationMap,
|
||||
}),
|
||||
},
|
||||
{
|
||||
attachTo: container,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getDefaultProps(
|
||||
{ datasourceMap = mockDatasourceMap(), visualizationMap = mockVisualizationMap() } = {
|
||||
datasourceMap: mockDatasourceMap(),
|
||||
visualizationMap: mockVisualizationMap(),
|
||||
}
|
||||
) {
|
||||
const lensAttributes = {
|
||||
title: 'test',
|
||||
visualizationType: 'testVis',
|
||||
state: {
|
||||
datasourceStates: {
|
||||
testDatasource: {},
|
||||
},
|
||||
visualization: {},
|
||||
filters: [],
|
||||
query: {
|
||||
language: 'lucene',
|
||||
query: '',
|
||||
},
|
||||
},
|
||||
filters: [],
|
||||
query: {
|
||||
language: 'lucene',
|
||||
query: '',
|
||||
},
|
||||
references: [],
|
||||
} as unknown as TypedLensByValueInput['attributes'];
|
||||
|
||||
const dataView = { id: 'index1', isPersisted: () => true } as unknown as DataView;
|
||||
return {
|
||||
attributes: lensAttributes,
|
||||
dataView,
|
||||
updateAll: jest.fn(),
|
||||
coreStart: coreMock.createStart(),
|
||||
startDependencies,
|
||||
visualizationMap,
|
||||
datasourceMap,
|
||||
setIsFlyoutVisible: jest.fn(),
|
||||
datasourceId: 'testDatasource',
|
||||
} as unknown as EditConfigPanelProps;
|
||||
}
|
||||
|
||||
it('should call the setIsFlyout callback if collapse button is clicked', async () => {
|
||||
const setIsFlyoutVisibleSpy = jest.fn();
|
||||
const props = getDefaultProps();
|
||||
const newProps = {
|
||||
...props,
|
||||
setIsFlyoutVisible: setIsFlyoutVisibleSpy,
|
||||
};
|
||||
const { instance } = await prepareAndMountComponent(newProps);
|
||||
expect(instance.find(EuiFlyoutBody).exists()).toBe(true);
|
||||
instance.find('[data-test-subj="collapseFlyoutButton"]').at(1).simulate('click');
|
||||
expect(setIsFlyoutVisibleSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should compute the frame public api correctly', async () => {
|
||||
const props = getDefaultProps();
|
||||
const { instance } = await prepareAndMountComponent(props);
|
||||
expect(instance.find(ConfigPanelWrapper).exists()).toBe(true);
|
||||
expect(instance.find(VisualizationToolbar).exists()).toBe(true);
|
||||
expect(instance.find(VisualizationToolbar).prop('framePublicAPI')).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"activeData": Object {},
|
||||
"dataViews": Object {
|
||||
"indexPatternRefs": Array [],
|
||||
"indexPatterns": Object {
|
||||
"index1": Object {
|
||||
"id": "index1",
|
||||
"isPersisted": [Function],
|
||||
},
|
||||
},
|
||||
},
|
||||
"datasourceLayers": Object {
|
||||
"a": Object {
|
||||
"datasourceId": "testDatasource",
|
||||
"getFilters": [MockFunction],
|
||||
"getMaxPossibleNumValues": [MockFunction],
|
||||
"getOperationForColumnId": [MockFunction],
|
||||
"getSourceId": [MockFunction],
|
||||
"getTableSpec": [MockFunction],
|
||||
"getVisualDefaults": [MockFunction],
|
||||
"hasDefaultTimeField": [MockFunction],
|
||||
"isTextBasedLanguage": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [],
|
||||
Array [],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": false,
|
||||
},
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"dateRange": Object {
|
||||
"fromDate": "2021-01-10T04:00:00.000Z",
|
||||
"toDate": "2021-01-10T08:00:00.000Z",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should compute the activeVisualization correctly', async () => {
|
||||
const props = getDefaultProps();
|
||||
const { instance } = await prepareAndMountComponent(props);
|
||||
expect(instance.find(VisualizationToolbar).prop('activeVisualization')).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"appendLayer": [MockFunction],
|
||||
"clearLayer": [MockFunction],
|
||||
"getConfiguration": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
Object {
|
||||
"frame": Object {
|
||||
"activeData": Object {},
|
||||
"dataViews": Object {
|
||||
"indexPatternRefs": Array [],
|
||||
"indexPatterns": Object {
|
||||
"index1": Object {
|
||||
"id": "index1",
|
||||
"isPersisted": [Function],
|
||||
},
|
||||
},
|
||||
},
|
||||
"datasourceLayers": Object {
|
||||
"a": Object {
|
||||
"datasourceId": "testDatasource",
|
||||
"getFilters": [MockFunction],
|
||||
"getMaxPossibleNumValues": [MockFunction],
|
||||
"getOperationForColumnId": [MockFunction],
|
||||
"getSourceId": [MockFunction],
|
||||
"getTableSpec": [MockFunction],
|
||||
"getVisualDefaults": [MockFunction],
|
||||
"hasDefaultTimeField": [MockFunction],
|
||||
"isTextBasedLanguage": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [],
|
||||
Array [],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": false,
|
||||
},
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"dateRange": Object {
|
||||
"fromDate": "2021-01-10T04:00:00.000Z",
|
||||
"toDate": "2021-01-10T08:00:00.000Z",
|
||||
},
|
||||
},
|
||||
"layerId": "layer1",
|
||||
"state": Object {},
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {
|
||||
"frame": Object {
|
||||
"activeData": Object {},
|
||||
"dataViews": Object {
|
||||
"indexPatternRefs": Array [],
|
||||
"indexPatterns": Object {
|
||||
"index1": Object {
|
||||
"id": "index1",
|
||||
"isPersisted": [Function],
|
||||
},
|
||||
},
|
||||
},
|
||||
"datasourceLayers": Object {
|
||||
"a": Object {
|
||||
"datasourceId": "testDatasource",
|
||||
"getFilters": [MockFunction],
|
||||
"getMaxPossibleNumValues": [MockFunction],
|
||||
"getOperationForColumnId": [MockFunction],
|
||||
"getSourceId": [MockFunction],
|
||||
"getTableSpec": [MockFunction],
|
||||
"getVisualDefaults": [MockFunction],
|
||||
"hasDefaultTimeField": [MockFunction],
|
||||
"isTextBasedLanguage": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [],
|
||||
Array [],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": false,
|
||||
},
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"dateRange": Object {
|
||||
"fromDate": "2021-01-10T04:00:00.000Z",
|
||||
"toDate": "2021-01-10T08:00:00.000Z",
|
||||
},
|
||||
},
|
||||
"layerId": "layer1",
|
||||
"state": Object {},
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": Object {
|
||||
"groups": Array [
|
||||
Object {
|
||||
"accessors": Array [],
|
||||
"dataTestSubj": "mockVisA",
|
||||
"filterOperations": [MockFunction],
|
||||
"groupId": "a",
|
||||
"groupLabel": "a",
|
||||
"layerId": "layer1",
|
||||
"supportsMoreColumns": true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": Object {
|
||||
"groups": Array [
|
||||
Object {
|
||||
"accessors": Array [],
|
||||
"dataTestSubj": "mockVisA",
|
||||
"filterOperations": [MockFunction],
|
||||
"groupId": "a",
|
||||
"groupLabel": "a",
|
||||
"layerId": "layer1",
|
||||
"supportsMoreColumns": true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"getDescription": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
Object {},
|
||||
],
|
||||
Array [
|
||||
Object {},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": Object {
|
||||
"label": "",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": Object {
|
||||
"label": "",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"getLayerIds": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
Object {},
|
||||
],
|
||||
Array [
|
||||
Object {},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": Array [
|
||||
"layer1",
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": Array [
|
||||
"layer1",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
"getLayerType": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
"layer1",
|
||||
Object {},
|
||||
],
|
||||
Array [
|
||||
"layer1",
|
||||
Object {},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": "data",
|
||||
},
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": "data",
|
||||
},
|
||||
],
|
||||
},
|
||||
"getRenderEventCounters": [MockFunction],
|
||||
"getSuggestions": [MockFunction],
|
||||
"getSupportedLayers": [MockFunction],
|
||||
"getVisualizationTypeId": [MockFunction],
|
||||
"id": "testVis",
|
||||
"initialize": [MockFunction],
|
||||
"removeDimension": [MockFunction],
|
||||
"removeLayer": [MockFunction],
|
||||
"renderDimensionEditor": [MockFunction],
|
||||
"setDimension": [MockFunction],
|
||||
"switchVisualizationType": [MockFunction],
|
||||
"toExpression": [MockFunction],
|
||||
"toPreviewExpression": [MockFunction],
|
||||
"visualizationTypes": Array [
|
||||
Object {
|
||||
"groupLabel": "testVisGroup",
|
||||
"icon": "empty",
|
||||
"id": "testVis",
|
||||
"label": "TEST",
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiSpacer,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
useEuiTheme,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { css } from '@emotion/react';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type { Datatable } from '@kbn/expressions-plugin/public';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { getResolvedDateRange } from '../../../utils';
|
||||
import type { LensPluginStartDependencies } from '../../../plugin';
|
||||
import {
|
||||
DataViewsState,
|
||||
useLensDispatch,
|
||||
updateStateFromSuggestion,
|
||||
} from '../../../state_management';
|
||||
import { VisualizationToolbar } from '../../../editor_frame_service/editor_frame/workspace_panel';
|
||||
|
||||
import type { DatasourceMap, VisualizationMap, DatasourceLayers } from '../../../types';
|
||||
import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component';
|
||||
import { ConfigPanelWrapper } from '../../../editor_frame_service/editor_frame/config_panel/config_panel';
|
||||
|
||||
export interface EditConfigPanelProps {
|
||||
attributes: TypedLensByValueInput['attributes'];
|
||||
dataView: DataView;
|
||||
updateAll: (datasourceState: unknown, visualizationState: unknown) => void;
|
||||
coreStart: CoreStart;
|
||||
startDependencies: LensPluginStartDependencies;
|
||||
visualizationMap: VisualizationMap;
|
||||
datasourceMap: DatasourceMap;
|
||||
setIsFlyoutVisible?: (flag: boolean) => void;
|
||||
datasourceId: 'formBased' | 'textBased';
|
||||
adaptersTables?: Record<string, Datatable>;
|
||||
}
|
||||
|
||||
export function LensEditConfigurationFlyout({
|
||||
attributes,
|
||||
dataView,
|
||||
coreStart,
|
||||
startDependencies,
|
||||
visualizationMap,
|
||||
datasourceMap,
|
||||
datasourceId,
|
||||
updateAll,
|
||||
setIsFlyoutVisible,
|
||||
adaptersTables,
|
||||
}: EditConfigPanelProps) {
|
||||
const currentDataViewId = dataView.id ?? '';
|
||||
const datasourceState = attributes.state.datasourceStates[datasourceId];
|
||||
const activeVisualization = visualizationMap[attributes.visualizationType];
|
||||
const activeDatasource = datasourceMap[datasourceId];
|
||||
const dispatchLens = useLensDispatch();
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const dataViews = useMemo(() => {
|
||||
return {
|
||||
indexPatterns: {
|
||||
[currentDataViewId]: dataView,
|
||||
},
|
||||
indexPatternRefs: [],
|
||||
} as unknown as DataViewsState;
|
||||
}, [currentDataViewId, dataView]);
|
||||
dispatchLens(
|
||||
updateStateFromSuggestion({
|
||||
newDatasourceId: datasourceId,
|
||||
visualizationId: activeVisualization.id,
|
||||
visualizationState: attributes.state.visualization,
|
||||
datasourceState,
|
||||
dataViews,
|
||||
})
|
||||
);
|
||||
|
||||
const datasourceLayers: DatasourceLayers = useMemo(() => {
|
||||
return {};
|
||||
}, []);
|
||||
const activeData: Record<string, Datatable> = useMemo(() => {
|
||||
return {};
|
||||
}, []);
|
||||
const layers = activeDatasource.getLayers(datasourceState);
|
||||
layers.forEach((layer) => {
|
||||
datasourceLayers[layer] = datasourceMap[datasourceId].getPublicAPI({
|
||||
state: datasourceState,
|
||||
layerId: layer,
|
||||
indexPatterns: dataViews.indexPatterns,
|
||||
});
|
||||
if (adaptersTables) {
|
||||
activeData[layer] = Object.values(adaptersTables)[0];
|
||||
}
|
||||
});
|
||||
|
||||
const dateRange = getResolvedDateRange(startDependencies.data.query.timefilter.timefilter);
|
||||
const framePublicAPI = useMemo(() => {
|
||||
return {
|
||||
activeData,
|
||||
dataViews,
|
||||
datasourceLayers,
|
||||
dateRange,
|
||||
};
|
||||
}, [activeData, dataViews, datasourceLayers, dateRange]);
|
||||
|
||||
const closeFlyout = () => {
|
||||
setIsFlyoutVisible?.(false);
|
||||
};
|
||||
|
||||
const layerPanelsProps = {
|
||||
framePublicAPI,
|
||||
datasourceMap,
|
||||
visualizationMap,
|
||||
core: coreStart,
|
||||
dataViews: startDependencies.dataViews,
|
||||
uiActions: startDependencies.uiActions,
|
||||
hideLayerHeader: true,
|
||||
onUpdateStateCb: updateAll,
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<EuiFlyoutBody
|
||||
className="lnsEditFlyoutBody"
|
||||
css={css`
|
||||
.euiFlyoutBody__overflowContent {
|
||||
padding: ${euiTheme.size.s};
|
||||
}
|
||||
`}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
title={i18n.translate('xpack.lens.config.configFlyoutCallout', {
|
||||
defaultMessage: 'SQL currently offers limited configuration options',
|
||||
})}
|
||||
iconType="iInCircle"
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<VisualizationToolbar
|
||||
activeVisualization={activeVisualization}
|
||||
framePublicAPI={framePublicAPI}
|
||||
onUpdateStateCb={updateAll}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<ConfigPanelWrapper {...layerPanelsProps} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiButtonEmpty
|
||||
onClick={closeFlyout}
|
||||
data-test-subj="collapseFlyoutButton"
|
||||
aria-controls="lens-config-close-button"
|
||||
aria-expanded="true"
|
||||
aria-label={i18n.translate('xpack.lens.config.closeFlyoutAriaLabel', {
|
||||
defaultMessage: 'Close flyout',
|
||||
})}
|
||||
>
|
||||
<FormattedMessage id="xpack.lens.config.closeFlyoutLabel" defaultMessage="Close" />
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlyoutFooter>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -30,6 +30,7 @@ export * from './visualizations/gauge/gauge_visualization';
|
|||
export * from './visualizations/gauge';
|
||||
export * from './visualizations/tagcloud/tagcloud_visualization';
|
||||
export * from './visualizations/tagcloud';
|
||||
export { getEditLensConfiguration } from './app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration';
|
||||
|
||||
export * from './datasources/form_based/form_based';
|
||||
export { getTextBasedDatasource } from './datasources/text_based/text_based_languages';
|
||||
|
|
|
@ -410,6 +410,22 @@ describe('Textbased Data Source', () => {
|
|||
);
|
||||
expect(suggestions[0].state).toEqual({
|
||||
...state,
|
||||
fieldList: [
|
||||
{
|
||||
id: 'newid',
|
||||
meta: {
|
||||
type: 'number',
|
||||
},
|
||||
name: 'bytes',
|
||||
},
|
||||
{
|
||||
id: 'newid',
|
||||
meta: {
|
||||
type: 'string',
|
||||
},
|
||||
name: 'dest',
|
||||
},
|
||||
],
|
||||
layers: {
|
||||
newid: {
|
||||
allColumns: [
|
||||
|
|
|
@ -122,6 +122,14 @@ export function getTextBasedDatasource({
|
|||
const query = context.query;
|
||||
const updatedState = {
|
||||
...state,
|
||||
fieldList:
|
||||
newColumns?.map((c) => {
|
||||
return {
|
||||
id: c.columnId,
|
||||
name: c.fieldName,
|
||||
meta: c.meta,
|
||||
};
|
||||
}) ?? [],
|
||||
layers: {
|
||||
...state.layers,
|
||||
[newLayerId]: {
|
||||
|
|
|
@ -31,12 +31,12 @@ export interface TextBasedLayer {
|
|||
|
||||
export interface TextBasedPersistedState {
|
||||
layers: Record<string, TextBasedLayer>;
|
||||
initialContext?: VisualizeFieldContext | VisualizeEditorContext;
|
||||
}
|
||||
|
||||
export type TextBasedPrivateState = TextBasedPersistedState & {
|
||||
indexPatternRefs: IndexPatternRef[];
|
||||
fieldList: DatatableColumn[];
|
||||
initialContext?: VisualizeFieldContext | VisualizeEditorContext;
|
||||
};
|
||||
|
||||
export interface IndexPatternRef {
|
||||
|
|
|
@ -170,13 +170,19 @@ describe('ConfigPanel', () => {
|
|||
|
||||
it('allow datasources and visualizations to use setters', async () => {
|
||||
const props = getDefaultProps();
|
||||
const { instance, lensStore } = await prepareAndMountComponent(props);
|
||||
const onUpdateCbSpy = jest.fn();
|
||||
const newProps = {
|
||||
...props,
|
||||
onUpdateStateCb: onUpdateCbSpy,
|
||||
};
|
||||
const { instance, lensStore } = await prepareAndMountComponent(newProps);
|
||||
const { updateDatasource, updateAll } = instance.find(LayerPanel).props();
|
||||
|
||||
const updater = () => 'updated';
|
||||
updateDatasource('testDatasource', updater);
|
||||
await waitMs(0);
|
||||
expect(lensStore.dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(onUpdateCbSpy).toHaveBeenCalled();
|
||||
expect(
|
||||
(lensStore.dispatch as jest.Mock).mock.calls[0][0].payload.updater(
|
||||
props.datasourceStates.testDatasource.state
|
||||
|
@ -184,6 +190,7 @@ describe('ConfigPanel', () => {
|
|||
).toEqual('updated');
|
||||
|
||||
updateAll('testDatasource', updater, props.visualizationState);
|
||||
expect(onUpdateCbSpy).toHaveBeenCalled();
|
||||
// wait for one tick so async updater has a chance to trigger
|
||||
await waitMs(0);
|
||||
expect(lensStore.dispatch).toHaveBeenCalledTimes(2);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { useMemo, memo, useCallback } from 'react';
|
||||
import { useStore } from 'react-redux';
|
||||
import { EuiForm } from '@elastic/eui';
|
||||
import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
|
||||
import { isOfAggregateQueryType } from '@kbn/es-query';
|
||||
|
@ -52,7 +53,8 @@ export function LayerPanels(
|
|||
activeVisualization: Visualization;
|
||||
}
|
||||
) {
|
||||
const { activeVisualization, datasourceMap, indexPatternService } = props;
|
||||
const lensStore = useStore();
|
||||
const { activeVisualization, datasourceMap, indexPatternService, onUpdateStateCb } = props;
|
||||
const { activeDatasourceId, visualization, datasourceStates, query } = useLensSelector(
|
||||
(state) => state.lens
|
||||
);
|
||||
|
@ -74,8 +76,12 @@ export function LayerPanels(
|
|||
newState,
|
||||
})
|
||||
);
|
||||
if (onUpdateStateCb && activeDatasourceId) {
|
||||
const dsState = datasourceStates[activeDatasourceId].state;
|
||||
onUpdateStateCb?.(dsState, newState);
|
||||
}
|
||||
},
|
||||
[activeVisualization, dispatchLens]
|
||||
[activeDatasourceId, activeVisualization.id, datasourceStates, dispatchLens, onUpdateStateCb]
|
||||
);
|
||||
const updateDatasource = useMemo(
|
||||
() =>
|
||||
|
@ -90,9 +96,10 @@ export function LayerPanels(
|
|||
dontSyncLinkedDimensions,
|
||||
})
|
||||
);
|
||||
onUpdateStateCb?.(newState, visualization.state);
|
||||
}
|
||||
},
|
||||
[dispatchLens]
|
||||
[dispatchLens, onUpdateStateCb, visualization.state]
|
||||
);
|
||||
const updateDatasourceAsync = useMemo(
|
||||
() => (datasourceId: string | undefined, newState: unknown) => {
|
||||
|
@ -147,9 +154,10 @@ export function LayerPanels(
|
|||
},
|
||||
})
|
||||
);
|
||||
onUpdateStateCb?.(newDatasourceState, newVisualizationState);
|
||||
}, 0);
|
||||
},
|
||||
[dispatchLens]
|
||||
[dispatchLens, onUpdateStateCb]
|
||||
);
|
||||
|
||||
const toggleFullscreen = useMemo(
|
||||
|
@ -213,20 +221,21 @@ export function LayerPanels(
|
|||
visualizationId?: string;
|
||||
layerId?: string;
|
||||
}) => {
|
||||
const indexPatterns = await props.indexPatternService.ensureIndexPattern({
|
||||
const indexPatterns = await props.indexPatternService?.ensureIndexPattern({
|
||||
id: indexPatternId,
|
||||
cache: props.framePublicAPI.dataViews.indexPatterns,
|
||||
});
|
||||
|
||||
dispatchLens(
|
||||
changeIndexPattern({
|
||||
indexPatternId,
|
||||
datasourceIds: datasourceId ? [datasourceId] : [],
|
||||
visualizationIds: visualizationId ? [visualizationId] : [],
|
||||
layerId,
|
||||
dataViews: { indexPatterns },
|
||||
})
|
||||
);
|
||||
if (indexPatterns) {
|
||||
dispatchLens(
|
||||
changeIndexPattern({
|
||||
indexPatternId,
|
||||
datasourceIds: datasourceId ? [datasourceId] : [],
|
||||
visualizationIds: visualizationId ? [visualizationId] : [],
|
||||
layerId,
|
||||
dataViews: { indexPatterns },
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
[dispatchLens, props.framePublicAPI.dataViews.indexPatterns, props.indexPatternService]
|
||||
);
|
||||
|
@ -262,6 +271,7 @@ export function LayerPanels(
|
|||
updateVisualization={setVisualizationState}
|
||||
updateDatasource={updateDatasource}
|
||||
updateDatasourceAsync={updateDatasourceAsync}
|
||||
displayLayerSettings={!props.hideLayerHeader}
|
||||
onChangeIndexPattern={(args) => {
|
||||
onChangeIndexPattern(args);
|
||||
const layersToRemove =
|
||||
|
@ -307,6 +317,13 @@ export function LayerPanels(
|
|||
const datasourcePublicAPI = props.framePublicAPI.datasourceLayers?.[layerId];
|
||||
const datasourceId = datasourcePublicAPI?.datasourceId;
|
||||
dispatchLens(removeDimension({ ...dimensionProps, datasourceId }));
|
||||
if (datasourceId && onUpdateStateCb) {
|
||||
const newState = lensStore.getState().lens;
|
||||
onUpdateStateCb(
|
||||
newState.datasourceStates[datasourceId].state,
|
||||
newState.visualization.state
|
||||
);
|
||||
}
|
||||
}}
|
||||
toggleFullscreen={toggleFullscreen}
|
||||
indexPatternService={indexPatternService}
|
||||
|
@ -336,19 +353,21 @@ export function LayerPanels(
|
|||
indexPatternId = dataView.id;
|
||||
}
|
||||
|
||||
const newIndexPatterns = await indexPatternService.ensureIndexPattern({
|
||||
const newIndexPatterns = await indexPatternService?.ensureIndexPattern({
|
||||
id: indexPatternId,
|
||||
cache: props.framePublicAPI.dataViews.indexPatterns,
|
||||
});
|
||||
|
||||
dispatchLens(
|
||||
changeIndexPattern({
|
||||
dataViews: { indexPatterns: newIndexPatterns },
|
||||
datasourceIds: Object.keys(datasourceStates),
|
||||
visualizationIds: visualization.activeId ? [visualization.activeId] : [],
|
||||
indexPatternId,
|
||||
})
|
||||
);
|
||||
if (newIndexPatterns) {
|
||||
dispatchLens(
|
||||
changeIndexPattern({
|
||||
dataViews: { indexPatterns: newIndexPatterns },
|
||||
datasourceIds: Object.keys(datasourceStates),
|
||||
visualizationIds: visualization.activeId ? [visualization.activeId] : [],
|
||||
indexPatternId,
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
registerLibraryAnnotationGroup: (groupInfo) =>
|
||||
dispatchLens(registerLibraryAnnotationGroup(groupInfo)),
|
||||
|
|
|
@ -11,6 +11,7 @@ import { EuiFormRow } from '@elastic/eui';
|
|||
import { ChildDragDropProvider, DragDrop } from '@kbn/dom-drag-drop';
|
||||
import { FramePublicAPI, Visualization, VisualizationConfigProps } from '../../../types';
|
||||
import { LayerPanel } from './layer_panel';
|
||||
import { LayerActions } from './layer_actions';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { generateId } from '../../../id_generator';
|
||||
import {
|
||||
|
@ -116,6 +117,7 @@ describe('LayerPanel', () => {
|
|||
onChangeIndexPattern: jest.fn(),
|
||||
indexPatternService: createIndexPatternServiceMock(),
|
||||
getUserMessages: () => [],
|
||||
displayLayerSettings: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -203,6 +205,13 @@ describe('LayerPanel', () => {
|
|||
expect(optionalLabel.text()).toEqual('Optional');
|
||||
});
|
||||
|
||||
it('should hide the layer actions if displayLayerSettings is set to false', async () => {
|
||||
const { instance } = await mountWithProvider(
|
||||
<LayerPanel {...getDefaultProps()} displayLayerSettings={false} />
|
||||
);
|
||||
expect(instance.find(LayerActions).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should render the group with a way to add a new column', async () => {
|
||||
mockVisualization.getConfiguration.mockReturnValue({
|
||||
groups: [
|
||||
|
|
|
@ -87,8 +87,9 @@ export function LayerPanel(
|
|||
datasourceId?: string;
|
||||
visualizationId?: string;
|
||||
}) => void;
|
||||
indexPatternService: IndexPatternServiceAPI;
|
||||
getUserMessages: UserMessagesGetter;
|
||||
indexPatternService?: IndexPatternServiceAPI;
|
||||
getUserMessages?: UserMessagesGetter;
|
||||
displayLayerSettings: boolean;
|
||||
}
|
||||
) {
|
||||
const [activeDimension, setActiveDimension] = useState<ActiveDimensionState>(
|
||||
|
@ -418,17 +419,20 @@ export function LayerPanel(
|
|||
activeVisualization={activeVisualization}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<LayerActions
|
||||
actions={compatibleActions}
|
||||
layerIndex={layerIndex}
|
||||
mountingPoint={layerActionsFlyoutRef.current}
|
||||
/>
|
||||
<div ref={layerActionsFlyoutRef} />
|
||||
</EuiFlexItem>
|
||||
{props.displayLayerSettings && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<LayerActions
|
||||
actions={compatibleActions}
|
||||
layerIndex={layerIndex}
|
||||
mountingPoint={layerActionsFlyoutRef.current}
|
||||
/>
|
||||
<div ref={layerActionsFlyoutRef} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
{(layerDatasource || activeVisualization.renderLayerPanel) && <EuiSpacer size="s" />}
|
||||
{layerDatasource && (
|
||||
{props.indexPatternService &&
|
||||
(layerDatasource || activeVisualization.renderLayerPanel) && <EuiSpacer size="s" />}
|
||||
{layerDatasource && props.indexPatternService && (
|
||||
<NativeRenderer
|
||||
render={layerDatasource.renderLayerPanel}
|
||||
nativeProps={{
|
||||
|
@ -544,9 +548,10 @@ export function LayerPanel(
|
|||
{group.accessors.map((accessorConfig, accessorIndex) => {
|
||||
const { columnId } = accessorConfig;
|
||||
|
||||
const messages = props.getUserMessages('dimensionButton', {
|
||||
dimensionId: columnId,
|
||||
});
|
||||
const messages =
|
||||
props?.getUserMessages?.('dimensionButton', {
|
||||
dimensionId: columnId,
|
||||
}) ?? [];
|
||||
|
||||
return (
|
||||
<DraggableDimensionButton
|
||||
|
|
|
@ -25,9 +25,11 @@ export interface ConfigPanelWrapperProps {
|
|||
visualizationMap: VisualizationMap;
|
||||
core: DatasourceDimensionEditorProps['core'];
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
indexPatternService: IndexPatternServiceAPI;
|
||||
indexPatternService?: IndexPatternServiceAPI;
|
||||
uiActions: UiActionsStart;
|
||||
getUserMessages: UserMessagesGetter;
|
||||
getUserMessages?: UserMessagesGetter;
|
||||
hideLayerHeader?: boolean;
|
||||
onUpdateStateCb?: (datasourceState: unknown, visualizationState: unknown) => void;
|
||||
}
|
||||
|
||||
export interface LayerPanelProps {
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
*/
|
||||
|
||||
export { WorkspacePanel } from './workspace_panel';
|
||||
export { VisualizationToolbar } from './workspace_panel_wrapper';
|
||||
|
|
|
@ -65,7 +65,13 @@ describe('workspace_panel_wrapper', () => {
|
|||
isFullscreen={false}
|
||||
lensInspector={{} as unknown as LensInspector}
|
||||
getUserMessages={() => []}
|
||||
/>
|
||||
/>,
|
||||
{
|
||||
preloadedState: {
|
||||
visualization: { activeId: 'myVis', state: visState },
|
||||
datasourceStates: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(renderToolbarMock).toHaveBeenCalledWith(expect.any(Element), {
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
FramePublicAPI,
|
||||
UserMessagesGetter,
|
||||
VisualizationMap,
|
||||
Visualization,
|
||||
} from '../../../types';
|
||||
import { DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from '../../../utils';
|
||||
import { NativeRenderer } from '../../../native_renderer';
|
||||
|
@ -49,6 +50,52 @@ export interface WorkspacePanelWrapperProps {
|
|||
getUserMessages: UserMessagesGetter;
|
||||
}
|
||||
|
||||
export function VisualizationToolbar(props: {
|
||||
activeVisualization: Visualization | null;
|
||||
framePublicAPI: FramePublicAPI;
|
||||
onUpdateStateCb?: (datasourceState: unknown, visualizationState: unknown) => void;
|
||||
}) {
|
||||
const dispatchLens = useLensDispatch();
|
||||
const { activeDatasourceId, visualization, datasourceStates } = useLensSelector(
|
||||
(state) => state.lens
|
||||
);
|
||||
const setVisualizationState = useCallback(
|
||||
(newState: unknown) => {
|
||||
if (!props.activeVisualization) {
|
||||
return;
|
||||
}
|
||||
dispatchLens(
|
||||
updateVisualizationState({
|
||||
visualizationId: props.activeVisualization.id,
|
||||
newState,
|
||||
})
|
||||
);
|
||||
if (activeDatasourceId && props.onUpdateStateCb) {
|
||||
const dsState = datasourceStates[activeDatasourceId].state;
|
||||
props.onUpdateStateCb?.(dsState, newState);
|
||||
}
|
||||
},
|
||||
[activeDatasourceId, datasourceStates, dispatchLens, props]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.activeVisualization && props.activeVisualization.renderToolbar && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<NativeRenderer
|
||||
render={props.activeVisualization.renderToolbar}
|
||||
nativeProps={{
|
||||
frame: props.framePublicAPI,
|
||||
state: visualization.state,
|
||||
setState: setVisualizationState,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function WorkspacePanelWrapper({
|
||||
children,
|
||||
framePublicAPI,
|
||||
|
@ -65,21 +112,6 @@ export function WorkspacePanelWrapper({
|
|||
const autoApplyEnabled = useLensSelector(selectAutoApplyEnabled);
|
||||
|
||||
const activeVisualization = visualizationId ? visualizationMap[visualizationId] : null;
|
||||
const setVisualizationState = useCallback(
|
||||
(newState: unknown) => {
|
||||
if (!activeVisualization) {
|
||||
return;
|
||||
}
|
||||
dispatchLens(
|
||||
updateVisualizationState({
|
||||
visualizationId: activeVisualization.id,
|
||||
newState,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatchLens, activeVisualization]
|
||||
);
|
||||
|
||||
const userMessages = getUserMessages('toolbar');
|
||||
|
||||
return (
|
||||
|
@ -116,19 +148,10 @@ export function WorkspacePanelWrapper({
|
|||
framePublicAPI={framePublicAPI}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
{activeVisualization && activeVisualization.renderToolbar && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<NativeRenderer
|
||||
render={activeVisualization.renderToolbar}
|
||||
nativeProps={{
|
||||
frame: framePublicAPI,
|
||||
state: visualizationState,
|
||||
setState: setVisualizationState,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<VisualizationToolbar
|
||||
activeVisualization={activeVisualization}
|
||||
framePublicAPI={framePublicAPI}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
|
|
@ -10,6 +10,8 @@ import { ExpressionsSetup, ExpressionsStart } from '@kbn/expressions-plugin/publ
|
|||
import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks';
|
||||
import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks';
|
||||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
|
||||
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
|
||||
import { EditorFrameSetupPlugins, EditorFrameStartPlugins } from './service';
|
||||
|
||||
|
@ -57,5 +59,7 @@ export function createMockStartDependencies() {
|
|||
embeddable: embeddablePluginMock.createStartContract(),
|
||||
expressions: expressionsPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
uiActions: uiActionsPluginMock.createStartContract(),
|
||||
dataViews: dataViewPluginMocks.createStartContract(),
|
||||
} as unknown as MockedStartDependencies;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
import type { LensByReferenceInput, LensByValueInput } from './embeddable';
|
||||
import type { Document } from '../persistence';
|
||||
import type { FormBasedPersistedState } from '../datasources/form_based/types';
|
||||
import type { TextBasedPersistedState } from '../datasources/text_based/types';
|
||||
import type { XYState } from '../visualizations/xy/types';
|
||||
import type {
|
||||
PieVisualizationState,
|
||||
|
@ -45,6 +46,7 @@ type LensAttributes<TVisType, TVisState> = Omit<
|
|||
state: Omit<Document['state'], 'datasourceStates' | 'visualization'> & {
|
||||
datasourceStates: {
|
||||
formBased: FormBasedPersistedState;
|
||||
textBased?: TextBasedPersistedState;
|
||||
};
|
||||
visualization: TVisState;
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ export const lensPluginMock = {
|
|||
SaveModalComponent: jest.fn(() => {
|
||||
return <span>Lens Save Modal Component</span>;
|
||||
}),
|
||||
EditLensConfigPanelApi: jest.fn().mockResolvedValue(<span>Lens Config Panel Component</span>),
|
||||
canUseEditor: jest.fn(() => true),
|
||||
navigateToPrefilledEditor: jest.fn(),
|
||||
getXyVisTypes: jest
|
||||
|
|
|
@ -126,6 +126,7 @@ import { type LensAppLocator, LensAppLocatorDefinition } from '../common/locator
|
|||
import { downloadCsvShareProvider } from './app_plugin/csv_download_provider/csv_download_provider';
|
||||
|
||||
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
|
||||
import type { EditLensConfigurationProps } from './app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration';
|
||||
|
||||
export interface LensPluginSetupDependencies {
|
||||
urlForwarding: UrlForwardingSetup;
|
||||
|
@ -215,6 +216,14 @@ export interface LensPublicStart {
|
|||
* @experimental
|
||||
*/
|
||||
SaveModalComponent: React.ComponentType<Omit<SaveModalContainerProps, 'lensServices'>>;
|
||||
/**
|
||||
* React component which can be used to embed a Lens Visualization Config Panel Component.
|
||||
*
|
||||
* This API might undergo breaking changes even in minor versions.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
EditLensConfigPanelApi: () => Promise<EditLensConfigPanelComponent>;
|
||||
/**
|
||||
* Method which navigates to the Lens editor, loading the state specified by the `input` parameter.
|
||||
* See `x-pack/examples/embedded_lens_example` for exemplary usage.
|
||||
|
@ -252,6 +261,8 @@ export interface LensPublicStart {
|
|||
}>;
|
||||
}
|
||||
|
||||
export type EditLensConfigPanelComponent = React.ComponentType<EditLensConfigurationProps>;
|
||||
|
||||
export type LensSuggestionsApi = (
|
||||
context: VisualizeFieldContext | VisualizeEditorContext,
|
||||
dataViews: DataView,
|
||||
|
@ -649,6 +660,17 @@ export class LensPlugin {
|
|||
},
|
||||
};
|
||||
},
|
||||
EditLensConfigPanelApi: async () => {
|
||||
const { getEditLensConfiguration } = await import('./async_services');
|
||||
if (!this.editorFrameService) {
|
||||
this.initDependenciesForApi();
|
||||
}
|
||||
const [visualizationMap, datasourceMap] = await Promise.all([
|
||||
this.editorFrameService!.loadVisualizations(),
|
||||
this.editorFrameService!.loadDatasources(),
|
||||
]);
|
||||
return getEditLensConfiguration(core, startDependencies, visualizationMap, datasourceMap);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ export const {
|
|||
submitSuggestion,
|
||||
switchDatasource,
|
||||
switchAndCleanDatasource,
|
||||
updateStateFromSuggestion,
|
||||
updateIndexPatterns,
|
||||
setToggleFullscreen,
|
||||
initEmpty,
|
||||
|
|
|
@ -10,6 +10,7 @@ import type { Query } from '@kbn/es-query';
|
|||
import {
|
||||
switchDatasource,
|
||||
switchAndCleanDatasource,
|
||||
updateStateFromSuggestion,
|
||||
switchVisualization,
|
||||
setState,
|
||||
updateState,
|
||||
|
@ -271,6 +272,28 @@ describe('lensSlice', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('update the state from the suggestion', () => {
|
||||
it('should switch active datasource and initialize new state', () => {
|
||||
store.dispatch(
|
||||
updateStateFromSuggestion({
|
||||
newDatasourceId: 'testDatasource2',
|
||||
visualizationId: 'testVis',
|
||||
visualizationState: ['col1', 'col2'],
|
||||
datasourceState: {},
|
||||
dataViews: { indexPatterns: {} } as DataViewsState,
|
||||
})
|
||||
);
|
||||
expect(store.getState().lens.activeDatasourceId).toEqual('testDatasource2');
|
||||
expect(store.getState().lens.datasourceStates.testDatasource2.isLoading).toEqual(false);
|
||||
expect(store.getState().lens.datasourceStates.testDatasource2.state).toStrictEqual({});
|
||||
expect(store.getState().lens.visualization).toStrictEqual({
|
||||
activeId: 'testVis',
|
||||
state: ['col1', 'col2'],
|
||||
});
|
||||
expect(store.getState().lens.dataViews).toEqual({ indexPatterns: {} });
|
||||
});
|
||||
});
|
||||
|
||||
describe('adding or removing layer', () => {
|
||||
const testDatasource = (datasourceId: string) => {
|
||||
return {
|
||||
|
|
|
@ -173,6 +173,13 @@ export const switchAndCleanDatasource = createAction<{
|
|||
visualizationId: string | null;
|
||||
currentIndexPatternId?: string;
|
||||
}>('lens/switchAndCleanDatasource');
|
||||
export const updateStateFromSuggestion = createAction<{
|
||||
newDatasourceId: string;
|
||||
visualizationId: string | null;
|
||||
visualizationState: unknown;
|
||||
datasourceState: unknown;
|
||||
dataViews: DataViewsState;
|
||||
}>('lens/updateStateFromSuggestion');
|
||||
export const navigateAway = createAction<void>('lens/navigateAway');
|
||||
export const loadInitial = createAction<{
|
||||
initialInput?: LensEmbeddableInput;
|
||||
|
@ -267,6 +274,7 @@ export const lensActions = {
|
|||
submitSuggestion,
|
||||
switchDatasource,
|
||||
switchAndCleanDatasource,
|
||||
updateStateFromSuggestion,
|
||||
navigateAway,
|
||||
loadInitial,
|
||||
initEmpty,
|
||||
|
@ -848,6 +856,42 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => {
|
|||
},
|
||||
};
|
||||
},
|
||||
[updateStateFromSuggestion.type]: (
|
||||
state,
|
||||
{
|
||||
payload,
|
||||
}: {
|
||||
payload: {
|
||||
newDatasourceId: string;
|
||||
visualizationId: string;
|
||||
visualizationState: unknown;
|
||||
datasourceState: unknown;
|
||||
dataViews: DataViewsState;
|
||||
};
|
||||
}
|
||||
) => {
|
||||
const visualization = {
|
||||
activeId: payload.visualizationId,
|
||||
state: payload.visualizationState,
|
||||
};
|
||||
|
||||
const datasourceState = payload.datasourceState;
|
||||
|
||||
return {
|
||||
...state,
|
||||
datasourceStates: {
|
||||
[payload.newDatasourceId]: {
|
||||
state: datasourceState,
|
||||
isLoading: false,
|
||||
},
|
||||
},
|
||||
activeDatasourceId: payload.newDatasourceId,
|
||||
visualization: {
|
||||
...visualization,
|
||||
},
|
||||
dataViews: payload.dataViews,
|
||||
};
|
||||
},
|
||||
[navigateAway.type]: (state) => state,
|
||||
[loadInitial.type]: (
|
||||
state,
|
||||
|
|
|
@ -5673,7 +5673,6 @@
|
|||
"unifiedHistogram.lensTitle": "Modifier la visualisation",
|
||||
"unifiedHistogram.resetChartHeight": "Réinitialiser à la hauteur par défaut",
|
||||
"unifiedHistogram.showChart": "Afficher le graphique",
|
||||
"unifiedHistogram.suggestionSelectorLabel": "Visualisation",
|
||||
"unifiedHistogram.suggestionSelectorPlaceholder": "Sélectionner la visualisation",
|
||||
"unifiedHistogram.timeIntervals": "Intervalles de temps",
|
||||
"unifiedHistogram.timeIntervalWithValueWarning": "Avertissement",
|
||||
|
|
|
@ -5674,7 +5674,6 @@
|
|||
"unifiedHistogram.lensTitle": "ビジュアライゼーションを編集",
|
||||
"unifiedHistogram.resetChartHeight": "デフォルトの高さにリセット",
|
||||
"unifiedHistogram.showChart": "グラフを表示",
|
||||
"unifiedHistogram.suggestionSelectorLabel": "ビジュアライゼーション",
|
||||
"unifiedHistogram.suggestionSelectorPlaceholder": "ビジュアライゼーションを選択",
|
||||
"unifiedHistogram.timeIntervals": "時間間隔",
|
||||
"unifiedHistogram.timeIntervalWithValueWarning": "警告",
|
||||
|
|
|
@ -5673,7 +5673,6 @@
|
|||
"unifiedHistogram.lensTitle": "编辑可视化",
|
||||
"unifiedHistogram.resetChartHeight": "重置为默认高度",
|
||||
"unifiedHistogram.showChart": "显示图表",
|
||||
"unifiedHistogram.suggestionSelectorLabel": "可视化",
|
||||
"unifiedHistogram.suggestionSelectorPlaceholder": "选择可视化",
|
||||
"unifiedHistogram.timeIntervals": "时间间隔",
|
||||
"unifiedHistogram.timeIntervalWithValueWarning": "警告",
|
||||
|
|
|
@ -156,11 +156,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await testSubjects.click('querySubmitButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('unifiedHistogramEditVisualization');
|
||||
await testSubjects.click('unifiedHistogramEditFlyoutVisualization');
|
||||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
await retry.waitFor('lens visualization', async () => {
|
||||
await retry.waitFor('lens flyout', async () => {
|
||||
const dimensions = await testSubjects.findAll('lns-dimensionTrigger-textBased');
|
||||
return dimensions.length === 2 && (await dimensions[1].getVisibleText()) === 'average';
|
||||
});
|
||||
|
@ -175,11 +175,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await testSubjects.click('querySubmitButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('unifiedHistogramEditVisualization');
|
||||
await testSubjects.click('unifiedHistogramEditFlyoutVisualization');
|
||||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
await retry.waitFor('lens visualization', async () => {
|
||||
await retry.waitFor('lens flyout', async () => {
|
||||
const dimensions = await testSubjects.findAll('lns-dimensionTrigger-textBased');
|
||||
return dimensions.length === 2 && (await dimensions[1].getVisibleText()) === 'average';
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue