mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Lens] Disable auto apply design updates (#128412)
This commit is contained in:
parent
02a146f7e4
commit
fefb24b717
20 changed files with 510 additions and 265 deletions
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { App } from './app';
|
||||
|
@ -83,6 +83,7 @@ describe('Lens App', () => {
|
|||
datasourceMap,
|
||||
visualizationMap,
|
||||
topNavMenuEntryGenerators: [],
|
||||
theme$: new Observable(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ export function App({
|
|||
contextOriginatingApp,
|
||||
topNavMenuEntryGenerators,
|
||||
initialContext,
|
||||
theme$,
|
||||
}: LensAppProps) {
|
||||
const lensAppServices = useKibana<LensAppServices>().services;
|
||||
|
||||
|
@ -402,6 +403,7 @@ export function App({
|
|||
initialContextIsEmbedded={initialContextIsEmbedded}
|
||||
topNavMenuEntryGenerators={topNavMenuEntryGenerators}
|
||||
initialContext={initialContext}
|
||||
theme$={theme$}
|
||||
/>
|
||||
{getLegacyUrlConflictCallout()}
|
||||
{(!isLoading || persistedDoc) && (
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { isEqual } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useStore } from 'react-redux';
|
||||
import { TopNavMenuData } from '../../../../../src/plugins/navigation/public';
|
||||
import {
|
||||
LensAppServices,
|
||||
|
@ -21,6 +22,7 @@ import { tableHasFormulas } from '../../../../../src/plugins/data/common';
|
|||
import { exporters } from '../../../../../src/plugins/data/public';
|
||||
import type { DataView } from '../../../../../src/plugins/data_views/public';
|
||||
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { toggleSettingsMenuOpen } from './settings_menu';
|
||||
import {
|
||||
setState,
|
||||
useLensSelector,
|
||||
|
@ -140,6 +142,17 @@ function getLensTopNavConfig(options: {
|
|||
tooltip: tooltips.showExportWarning,
|
||||
});
|
||||
|
||||
topNavMenu.push({
|
||||
label: i18n.translate('xpack.lens.app.settings', {
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
run: actions.openSettings,
|
||||
testId: 'lnsApp_settingsButton',
|
||||
description: i18n.translate('xpack.lens.app.settingsAriaLabel', {
|
||||
defaultMessage: 'Open the Lens settings menu',
|
||||
}),
|
||||
});
|
||||
|
||||
if (showCancel) {
|
||||
topNavMenu.push({
|
||||
label: i18n.translate('xpack.lens.app.cancel', {
|
||||
|
@ -200,6 +213,7 @@ export const LensTopNavMenu = ({
|
|||
initialContextIsEmbedded,
|
||||
topNavMenuEntryGenerators,
|
||||
initialContext,
|
||||
theme$,
|
||||
}: LensTopNavMenuProps) => {
|
||||
const {
|
||||
data,
|
||||
|
@ -233,6 +247,7 @@ export const LensTopNavMenu = ({
|
|||
visualization,
|
||||
filters,
|
||||
} = useLensSelector((state) => state.lens);
|
||||
|
||||
const allLoaded = Object.values(datasourceStates).every(({ isLoading }) => isLoading === false);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -337,6 +352,8 @@ export const LensTopNavMenu = ({
|
|||
application.capabilities,
|
||||
]);
|
||||
|
||||
const lensStore = useStore();
|
||||
|
||||
const topNavConfig = useMemo(() => {
|
||||
const baseMenuEntries = getLensTopNavConfig({
|
||||
showSaveAndReturn:
|
||||
|
@ -465,6 +482,12 @@ export const LensTopNavMenu = ({
|
|||
columns: meta.columns,
|
||||
});
|
||||
},
|
||||
openSettings: (anchorElement: HTMLElement) =>
|
||||
toggleSettingsMenuOpen({
|
||||
lensStore,
|
||||
anchorElement,
|
||||
theme$,
|
||||
}),
|
||||
},
|
||||
});
|
||||
return [...(additionalMenuEntries || []), ...baseMenuEntries];
|
||||
|
@ -497,6 +520,8 @@ export const LensTopNavMenu = ({
|
|||
filters,
|
||||
indexPatterns,
|
||||
data.query.timefilter.timefilter,
|
||||
lensStore,
|
||||
theme$,
|
||||
]);
|
||||
|
||||
const onQuerySubmitWrapped = useCallback(
|
||||
|
|
|
@ -249,6 +249,7 @@ export async function mountApp(
|
|||
initialContext={initialContext}
|
||||
contextOriginatingApp={historyLocationState?.originatingApp}
|
||||
topNavMenuEntryGenerators={topNavMenuEntryGenerators}
|
||||
theme$={core.theme.theme$}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
|
|
108
x-pack/plugins/lens/public/app_plugin/settings_menu.tsx
Normal file
108
x-pack/plugins/lens/public/app_plugin/settings_menu.tsx
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* 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, { useCallback } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import type { CoreTheme } from 'kibana/public';
|
||||
import { EuiPopoverTitle, EuiSwitch, EuiWrappingPopover } from '@elastic/eui';
|
||||
import { Observable } from 'rxjs';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Store } from 'redux';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
import {
|
||||
disableAutoApply,
|
||||
enableAutoApply,
|
||||
LensAppState,
|
||||
selectAutoApplyEnabled,
|
||||
useLensDispatch,
|
||||
useLensSelector,
|
||||
} from '../state_management';
|
||||
import { trackUiEvent } from '../lens_ui_telemetry';
|
||||
import { writeToStorage } from '../settings_storage';
|
||||
import { AUTO_APPLY_DISABLED_STORAGE_KEY } from '../editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper';
|
||||
|
||||
const container = document.createElement('div');
|
||||
let isOpen = false;
|
||||
|
||||
function SettingsMenu({
|
||||
anchorElement,
|
||||
onClose,
|
||||
}: {
|
||||
anchorElement: HTMLElement;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const autoApplyEnabled = useLensSelector(selectAutoApplyEnabled);
|
||||
|
||||
const dispatch = useLensDispatch();
|
||||
|
||||
const toggleAutoApply = useCallback(() => {
|
||||
trackUiEvent('toggle_autoapply');
|
||||
|
||||
writeToStorage(
|
||||
new Storage(localStorage),
|
||||
AUTO_APPLY_DISABLED_STORAGE_KEY,
|
||||
String(autoApplyEnabled)
|
||||
);
|
||||
dispatch(autoApplyEnabled ? disableAutoApply() : enableAutoApply());
|
||||
}, [dispatch, autoApplyEnabled]);
|
||||
|
||||
return (
|
||||
<EuiWrappingPopover
|
||||
data-test-subj="lnsApp__settingsMenu"
|
||||
ownFocus
|
||||
button={anchorElement}
|
||||
closePopover={onClose}
|
||||
isOpen
|
||||
>
|
||||
<EuiPopoverTitle>
|
||||
<FormattedMessage id="xpack.lens.settings.title" defaultMessage="Lens settings" />
|
||||
</EuiPopoverTitle>
|
||||
<EuiSwitch
|
||||
label={i18n.translate('xpack.lens.settings.autoApply', {
|
||||
defaultMessage: 'Auto-apply visualization changes',
|
||||
})}
|
||||
checked={autoApplyEnabled}
|
||||
onChange={() => toggleAutoApply()}
|
||||
data-test-subj="lnsToggleAutoApply"
|
||||
/>
|
||||
</EuiWrappingPopover>
|
||||
);
|
||||
}
|
||||
|
||||
function closeSettingsMenu() {
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
document.body.removeChild(container);
|
||||
isOpen = false;
|
||||
}
|
||||
|
||||
export function toggleSettingsMenuOpen(props: {
|
||||
lensStore: Store<LensAppState>;
|
||||
anchorElement: HTMLElement;
|
||||
theme$: Observable<CoreTheme>;
|
||||
}) {
|
||||
if (isOpen) {
|
||||
closeSettingsMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
isOpen = true;
|
||||
document.body.appendChild(container);
|
||||
|
||||
const element = (
|
||||
<Provider store={props.lensStore}>
|
||||
<KibanaThemeProvider theme$={props.theme$}>
|
||||
<I18nProvider>
|
||||
<SettingsMenu {...props} onClose={closeSettingsMenu} />
|
||||
</I18nProvider>
|
||||
</KibanaThemeProvider>
|
||||
</Provider>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
}
|
|
@ -8,11 +8,13 @@
|
|||
import type { History } from 'history';
|
||||
import type { OnSaveProps } from 'src/plugins/saved_objects/public';
|
||||
import { DiscoverStart } from 'src/plugins/discover/public';
|
||||
import { Observable } from 'rxjs';
|
||||
import { SpacesApi } from '../../../spaces/public';
|
||||
import type {
|
||||
ApplicationStart,
|
||||
AppMountParameters,
|
||||
ChromeStart,
|
||||
CoreTheme,
|
||||
ExecutionContextStart,
|
||||
HttpStart,
|
||||
IUiSettingsClient,
|
||||
|
@ -73,6 +75,7 @@ export interface LensAppProps {
|
|||
initialContext?: VisualizeEditorContext | VisualizeFieldContext;
|
||||
contextOriginatingApp?: string;
|
||||
topNavMenuEntryGenerators: LensTopNavMenuEntryGenerator[];
|
||||
theme$: Observable<CoreTheme>;
|
||||
}
|
||||
|
||||
export type RunSave = (
|
||||
|
@ -107,6 +110,7 @@ export interface LensTopNavMenuProps {
|
|||
initialContextIsEmbedded?: boolean;
|
||||
topNavMenuEntryGenerators: LensTopNavMenuEntryGenerator[];
|
||||
initialContext?: VisualizeFieldContext | VisualizeEditorContext;
|
||||
theme$: Observable<CoreTheme>;
|
||||
}
|
||||
|
||||
export interface HistoryLocationState {
|
||||
|
@ -157,4 +161,5 @@ export interface LensTopNavActions {
|
|||
cancel: () => void;
|
||||
exportToCSV: () => void;
|
||||
getUnderlyingDataUrl: () => string | undefined;
|
||||
openSettings: (anchorElement: HTMLElement) => void;
|
||||
}
|
||||
|
|
BIN
x-pack/plugins/lens/public/assets/render_dark@2x.png
Normal file
BIN
x-pack/plugins/lens/public/assets/render_dark@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
x-pack/plugins/lens/public/assets/render_light@2x.png
Normal file
BIN
x-pack/plugins/lens/public/assets/render_light@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
|
@ -12,6 +12,8 @@
|
|||
// Padding / negative margins to make room for overflow shadow
|
||||
padding-left: $euiSizeXS;
|
||||
margin-left: -$euiSizeXS;
|
||||
padding-right: $euiSizeXS;
|
||||
margin-right: -$euiSizeXS;
|
||||
}
|
||||
|
||||
.lnsSuggestionPanel {
|
||||
|
@ -91,4 +93,10 @@
|
|||
|
||||
.lnsSuggestionPanel__applyChangesPrompt {
|
||||
height: $lnsSuggestionHeight;
|
||||
background-color: $euiColorLightestShade !important;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import {
|
|||
import { setChangesApplied } from '../../state_management/lens_slice';
|
||||
|
||||
const SELECTORS = {
|
||||
APPLY_CHANGES_BUTTON: 'button[data-test-subj="lnsSuggestionApplyChanges"]',
|
||||
APPLY_CHANGES_BUTTON: 'button[data-test-subj="lnsApplyChanges__suggestions"]',
|
||||
SUGGESTIONS_PANEL: '[data-test-subj="lnsSuggestionsPanel"]',
|
||||
SUGGESTION_TILE_BUTTON: 'button[data-test-subj="lnsSuggestion"]',
|
||||
};
|
||||
|
|
|
@ -19,10 +19,7 @@ import {
|
|||
EuiToolTip,
|
||||
EuiButtonEmpty,
|
||||
EuiAccordion,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButton,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { IconType } from '@elastic/eui/src/components/icon/icon';
|
||||
import { Ast, toExpression } from '@kbn/interpreter';
|
||||
|
@ -337,41 +334,33 @@ export function SuggestionPanel({
|
|||
}
|
||||
}
|
||||
|
||||
const applyChangesPrompt = (
|
||||
<EuiPanel
|
||||
hasBorder
|
||||
hasShadow={false}
|
||||
className="lnsSuggestionPanel__applyChangesPrompt"
|
||||
paddingSize="m"
|
||||
>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.suggestions.applyChangesPrompt"
|
||||
defaultMessage="Apply your changes to see suggestions."
|
||||
/>
|
||||
</h3>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiButton
|
||||
fill
|
||||
iconType="play"
|
||||
size="s"
|
||||
className={DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS}
|
||||
onClick={() => dispatchLens(applyChanges())}
|
||||
data-test-subj="lnsSuggestionApplyChanges"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.suggestions.applyChangesLabel"
|
||||
defaultMessage="Apply"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
const renderApplyChangesPrompt = () => (
|
||||
<EuiPanel hasShadow={false} className="lnsSuggestionPanel__applyChangesPrompt" paddingSize="m">
|
||||
<EuiText size="s" color="subdued" className="lnsSuggestionPanel__applyChangesMessage">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.suggestions.applyChangesPrompt"
|
||||
defaultMessage="Latest changes must be applied to view suggestions."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiButtonEmpty
|
||||
iconType="checkInCircleFilled"
|
||||
size="s"
|
||||
className={DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS}
|
||||
onClick={() => dispatchLens(applyChanges())}
|
||||
data-test-subj="lnsApplyChanges__suggestions"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.suggestions.applyChangesLabel"
|
||||
defaultMessage="Apply changes"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiPanel>
|
||||
);
|
||||
|
||||
const suggestionsUI = (
|
||||
const renderSuggestionsUI = () => (
|
||||
<>
|
||||
{currentVisualization.activeId && !hideSuggestions && (
|
||||
<SuggestionPreview
|
||||
|
@ -461,7 +450,7 @@ export function SuggestionPanel({
|
|||
}
|
||||
>
|
||||
<div className="lnsSuggestionPanel__suggestions" data-test-subj="lnsSuggestionsPanel">
|
||||
{changesApplied ? suggestionsUI : applyChangesPrompt}
|
||||
{changesApplied ? renderSuggestionsUI() : renderApplyChangesPrompt()}
|
||||
</div>
|
||||
</EuiAccordion>
|
||||
</div>
|
||||
|
|
|
@ -52,7 +52,7 @@ export function GeoFieldWorkspacePanel(props: Props) {
|
|||
<h2>
|
||||
<strong>{getVisualizeGeoFieldMessage(props.fieldType)}</strong>
|
||||
</h2>
|
||||
<GlobeIllustration aria-hidden={true} className="lnsWorkspacePanel__dropIllustration" />
|
||||
<GlobeIllustration aria-hidden={true} className="lnsWorkspacePanel__promptIllustration" />
|
||||
<DragDrop
|
||||
className="lnsVisualizeGeoFieldWorkspacePanel__dragDrop"
|
||||
dataTestSubj="lnsGeoFieldWorkspace"
|
||||
|
|
|
@ -73,6 +73,12 @@ const defaultProps = {
|
|||
toggleFullscreen: jest.fn(),
|
||||
};
|
||||
|
||||
const SELECTORS = {
|
||||
applyChangesButton: 'button[data-test-subj="lnsApplyChanges__toolbar"]',
|
||||
dragDropPrompt: '[data-test-subj="workspace-drag-drop-prompt"]',
|
||||
applyChangesPrompt: '[data-test-subj="workspace-apply-changes-prompt"]',
|
||||
};
|
||||
|
||||
describe('workspace_panel', () => {
|
||||
let mockVisualization: jest.Mocked<Visualization>;
|
||||
let mockVisualization2: jest.Mocked<Visualization>;
|
||||
|
@ -115,7 +121,7 @@ describe('workspace_panel', () => {
|
|||
instance = mounted.instance;
|
||||
instance.update();
|
||||
|
||||
expect(instance.find('[data-test-subj="empty-workspace"]')).toHaveLength(2);
|
||||
expect(instance.find('[data-test-subj="workspace-drag-drop-prompt"]')).toHaveLength(2);
|
||||
expect(instance.find(expressionRendererMock)).toHaveLength(0);
|
||||
});
|
||||
|
||||
|
@ -133,7 +139,7 @@ describe('workspace_panel', () => {
|
|||
instance = mounted.instance;
|
||||
instance.update();
|
||||
|
||||
expect(instance.find('[data-test-subj="empty-workspace"]')).toHaveLength(2);
|
||||
expect(instance.find('[data-test-subj="workspace-drag-drop-prompt"]')).toHaveLength(2);
|
||||
expect(instance.find(expressionRendererMock)).toHaveLength(0);
|
||||
});
|
||||
|
||||
|
@ -151,7 +157,7 @@ describe('workspace_panel', () => {
|
|||
instance = mounted.instance;
|
||||
instance.update();
|
||||
|
||||
expect(instance.find('[data-test-subj="empty-workspace"]')).toHaveLength(2);
|
||||
expect(instance.find('[data-test-subj="workspace-drag-drop-prompt"]')).toHaveLength(2);
|
||||
expect(instance.find(expressionRendererMock)).toHaveLength(0);
|
||||
});
|
||||
|
||||
|
@ -218,8 +224,10 @@ describe('workspace_panel', () => {
|
|||
instance = mounted.instance;
|
||||
instance.update();
|
||||
|
||||
const getExpression = () => instance.find(expressionRendererMock).prop('expression');
|
||||
|
||||
// allows initial render
|
||||
expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(`
|
||||
expect(getExpression()).toMatchInlineSnapshot(`
|
||||
"kibana
|
||||
| lens_merge_tables layerIds=\\"first\\" tables={datasource}
|
||||
| testVis"
|
||||
|
@ -233,9 +241,9 @@ describe('workspace_panel', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
instance.update();
|
||||
|
||||
// nothing should change
|
||||
expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(`
|
||||
expect(getExpression()).toMatchInlineSnapshot(`
|
||||
"kibana
|
||||
| lens_merge_tables layerIds=\\"first\\" tables={datasource}
|
||||
| testVis"
|
||||
|
@ -247,7 +255,7 @@ describe('workspace_panel', () => {
|
|||
instance.update();
|
||||
|
||||
// should update
|
||||
expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(`
|
||||
expect(getExpression()).toMatchInlineSnapshot(`
|
||||
"kibana
|
||||
| lens_merge_tables layerIds=\\"first\\" tables={new-datasource}
|
||||
| new-vis"
|
||||
|
@ -260,22 +268,12 @@ describe('workspace_panel', () => {
|
|||
testVis: { ...mockVisualization, toExpression: () => 'other-new-vis' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// should not update
|
||||
expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(`
|
||||
"kibana
|
||||
| lens_merge_tables layerIds=\\"first\\" tables={new-datasource}
|
||||
| new-vis"
|
||||
`);
|
||||
|
||||
act(() => {
|
||||
mounted.lensStore.dispatch(enableAutoApply());
|
||||
});
|
||||
instance.update();
|
||||
|
||||
// reenabling auto-apply triggers an update as well
|
||||
expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(`
|
||||
expect(getExpression()).toMatchInlineSnapshot(`
|
||||
"kibana
|
||||
| lens_merge_tables layerIds=\\"first\\" tables={other-new-datasource}
|
||||
| other-new-vis"
|
||||
|
@ -339,13 +337,41 @@ describe('workspace_panel', () => {
|
|||
expect(isSaveable()).toBe(false);
|
||||
});
|
||||
|
||||
it('should allow empty workspace as initial render when auto-apply disabled', async () => {
|
||||
mockVisualization.toExpression.mockReturnValue('testVis');
|
||||
it('should show proper workspace prompts when auto-apply disabled', async () => {
|
||||
const framePublicAPI = createMockFramePublicAPI();
|
||||
framePublicAPI.datasourceLayers = {
|
||||
first: mockDatasource.publicAPIMock,
|
||||
};
|
||||
|
||||
const configureValidVisualization = () => {
|
||||
mockVisualization.toExpression.mockReturnValue('testVis');
|
||||
mockDatasource.toExpression.mockReturnValue('datasource');
|
||||
mockDatasource.getLayers.mockReturnValue(['first']);
|
||||
act(() => {
|
||||
instance.setProps({
|
||||
visualizationMap: {
|
||||
testVis: { ...mockVisualization, toExpression: () => 'new-vis' },
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const deleteVisualization = () => {
|
||||
act(() => {
|
||||
instance.setProps({
|
||||
visualizationMap: {
|
||||
testVis: { ...mockVisualization, toExpression: () => null },
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const dragDropPromptShowing = () => instance.exists(SELECTORS.dragDropPrompt);
|
||||
|
||||
const applyChangesPromptShowing = () => instance.exists(SELECTORS.applyChangesPrompt);
|
||||
|
||||
const visualizationShowing = () => instance.exists(expressionRendererMock);
|
||||
|
||||
const mounted = await mountWithProvider(
|
||||
<WorkspacePanel
|
||||
{...defaultProps}
|
||||
|
@ -356,6 +382,7 @@ describe('workspace_panel', () => {
|
|||
visualizationMap={{
|
||||
testVis: mockVisualization,
|
||||
}}
|
||||
ExpressionRenderer={expressionRendererMock}
|
||||
/>,
|
||||
{
|
||||
preloadedState: {
|
||||
|
@ -367,7 +394,37 @@ describe('workspace_panel', () => {
|
|||
instance = mounted.instance;
|
||||
instance.update();
|
||||
|
||||
expect(instance.exists('[data-test-subj="empty-workspace"]')).toBeTruthy();
|
||||
expect(dragDropPromptShowing()).toBeTruthy();
|
||||
|
||||
configureValidVisualization();
|
||||
instance.update();
|
||||
|
||||
expect(dragDropPromptShowing()).toBeFalsy();
|
||||
expect(applyChangesPromptShowing()).toBeTruthy();
|
||||
|
||||
instance.find(SELECTORS.applyChangesButton).simulate('click');
|
||||
instance.update();
|
||||
|
||||
expect(visualizationShowing()).toBeTruthy();
|
||||
|
||||
deleteVisualization();
|
||||
instance.update();
|
||||
|
||||
expect(visualizationShowing()).toBeTruthy();
|
||||
|
||||
act(() => {
|
||||
mounted.lensStore.dispatch(applyChanges());
|
||||
});
|
||||
instance.update();
|
||||
|
||||
expect(visualizationShowing()).toBeFalsy();
|
||||
expect(dragDropPromptShowing()).toBeTruthy();
|
||||
|
||||
configureValidVisualization();
|
||||
instance.update();
|
||||
|
||||
expect(dragDropPromptShowing()).toBeFalsy();
|
||||
expect(applyChangesPromptShowing()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should execute a trigger on expression event', async () => {
|
||||
|
@ -921,9 +978,7 @@ describe('workspace_panel', () => {
|
|||
expect(showingErrors()).toBeFalsy();
|
||||
|
||||
// errors should appear when problem changes are applied
|
||||
act(() => {
|
||||
lensStore.dispatch(applyChanges());
|
||||
});
|
||||
instance.find(SELECTORS.applyChangesButton).simulate('click');
|
||||
instance.update();
|
||||
|
||||
expect(showingErrors()).toBeTruthy();
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
EuiPageContentBody,
|
||||
EuiButton,
|
||||
EuiSpacer,
|
||||
EuiTextColor,
|
||||
} from '@elastic/eui';
|
||||
import type { CoreStart, ApplicationStart } from 'kibana/public';
|
||||
import type { DataPublicPluginStart, ExecutionContextSearch } from 'src/plugins/data/public';
|
||||
|
@ -47,6 +48,8 @@ import { UiActionsStart } from '../../../../../../../src/plugins/ui_actions/publ
|
|||
import { VIS_EVENT_TO_TRIGGER } from '../../../../../../../src/plugins/visualizations/public';
|
||||
import { WorkspacePanelWrapper } from './workspace_panel_wrapper';
|
||||
import { DropIllustration } from '../../../assets/drop_illustration';
|
||||
import applyChangesIllustrationDark from '../../../assets/render_dark@2x.png';
|
||||
import applyChangesIllustrationLight from '../../../assets/render_light@2x.png';
|
||||
import {
|
||||
getOriginalRequestErrorMessages,
|
||||
getUnknownVisualizationTypeError,
|
||||
|
@ -69,11 +72,14 @@ import {
|
|||
selectAutoApplyEnabled,
|
||||
selectTriggerApplyChanges,
|
||||
selectDatasourceLayers,
|
||||
applyChanges,
|
||||
selectChangesApplied,
|
||||
} from '../../../state_management';
|
||||
import type { LensInspector } from '../../../lens_inspector_service';
|
||||
import { inferTimeField } from '../../../utils';
|
||||
import { setChangesApplied } from '../../../state_management/lens_slice';
|
||||
import type { Datatable } from '../../../../../../../src/plugins/expressions/public';
|
||||
import { DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from '../config_panel/dimension_container';
|
||||
|
||||
export interface WorkspacePanelProps {
|
||||
visualizationMap: VisualizationMap;
|
||||
|
@ -143,6 +149,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
const activeDatasourceId = useLensSelector(selectActiveDatasourceId);
|
||||
const datasourceStates = useLensSelector(selectDatasourceStates);
|
||||
const autoApplyEnabled = useLensSelector(selectAutoApplyEnabled);
|
||||
const changesApplied = useLensSelector(selectChangesApplied);
|
||||
const triggerApply = useLensSelector(selectTriggerApplyChanges);
|
||||
|
||||
const [localState, setLocalState] = useState<WorkspaceState>({
|
||||
|
@ -201,7 +208,8 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
[activeVisualization, visualization.state, activeDatasourceId, datasourceMap, datasourceStates]
|
||||
);
|
||||
|
||||
const _expression = useMemo(() => {
|
||||
// if the expression is undefined, it means we hit an error that should be displayed to the user
|
||||
const unappliedExpression = useMemo(() => {
|
||||
if (!configurationValidationError?.length && !missingRefsErrors.length && !unknownVisError) {
|
||||
try {
|
||||
const ast = buildExpression({
|
||||
|
@ -254,20 +262,23 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatchLens(setSaveable(Boolean(_expression)));
|
||||
}, [_expression, dispatchLens]);
|
||||
dispatchLens(setSaveable(Boolean(unappliedExpression)));
|
||||
}, [unappliedExpression, dispatchLens]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!autoApplyEnabled) {
|
||||
dispatchLens(setChangesApplied(_expression === localState.expressionToRender));
|
||||
dispatchLens(setChangesApplied(unappliedExpression === localState.expressionToRender));
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldApplyExpression) {
|
||||
setLocalState((s) => ({ ...s, expressionToRender: _expression }));
|
||||
setLocalState((s) => ({
|
||||
...s,
|
||||
expressionToRender: unappliedExpression,
|
||||
}));
|
||||
}
|
||||
}, [_expression, shouldApplyExpression]);
|
||||
}, [unappliedExpression, shouldApplyExpression]);
|
||||
|
||||
const expressionExists = Boolean(localState.expressionToRender);
|
||||
useEffect(() => {
|
||||
|
@ -332,15 +343,23 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
}
|
||||
}, [suggestionForDraggedField, expressionExists, dispatchLens]);
|
||||
|
||||
const renderEmptyWorkspace = () => {
|
||||
const IS_DARK_THEME = core.uiSettings.get('theme:darkMode');
|
||||
|
||||
const renderDragDropPrompt = () => {
|
||||
return (
|
||||
<EuiText
|
||||
className={classNames('lnsWorkspacePanel__emptyContent')}
|
||||
textAlign="center"
|
||||
color="subdued"
|
||||
data-test-subj="empty-workspace"
|
||||
data-test-subj="workspace-drag-drop-prompt"
|
||||
size="s"
|
||||
>
|
||||
<DropIllustration
|
||||
aria-hidden={true}
|
||||
className={classNames(
|
||||
'lnsWorkspacePanel__promptIllustration',
|
||||
'lnsWorkspacePanel__dropIllustration'
|
||||
)}
|
||||
/>
|
||||
<h2>
|
||||
<strong>
|
||||
{!expressionExists
|
||||
|
@ -352,26 +371,25 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
})}
|
||||
</strong>
|
||||
</h2>
|
||||
<DropIllustration aria-hidden={true} className="lnsWorkspacePanel__dropIllustration" />
|
||||
{!expressionExists && (
|
||||
<>
|
||||
<p>
|
||||
{i18n.translate('xpack.lens.editorFrame.emptyWorkspaceHeading', {
|
||||
defaultMessage: 'Lens is the recommended editor for creating visualizations',
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
<small>
|
||||
<EuiLink
|
||||
href="https://www.elastic.co/products/kibana/feedback"
|
||||
target="_blank"
|
||||
external
|
||||
>
|
||||
{i18n.translate('xpack.lens.editorFrame.goToForums', {
|
||||
defaultMessage: 'Make requests and give feedback',
|
||||
})}
|
||||
</EuiLink>
|
||||
</small>
|
||||
<EuiTextColor color="subdued" component="div">
|
||||
<p>
|
||||
{i18n.translate('xpack.lens.editorFrame.emptyWorkspaceHeading', {
|
||||
defaultMessage: 'Lens is the recommended editor for creating visualizations',
|
||||
})}
|
||||
</p>
|
||||
</EuiTextColor>
|
||||
<p className="lnsWorkspacePanel__actions">
|
||||
<EuiLink
|
||||
href="https://www.elastic.co/products/kibana/feedback"
|
||||
target="_blank"
|
||||
external
|
||||
>
|
||||
{i18n.translate('xpack.lens.editorFrame.goToForums', {
|
||||
defaultMessage: 'Make requests and give feedback',
|
||||
})}
|
||||
</EuiLink>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
@ -379,11 +397,47 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
);
|
||||
};
|
||||
|
||||
const renderVisualization = () => {
|
||||
if (localState.expressionToRender === null) {
|
||||
return renderEmptyWorkspace();
|
||||
}
|
||||
const renderApplyChangesPrompt = () => {
|
||||
const applyChangesString = i18n.translate('xpack.lens.editorFrame.applyChanges', {
|
||||
defaultMessage: 'Apply changes',
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiText
|
||||
className={classNames('lnsWorkspacePanel__emptyContent')}
|
||||
textAlign="center"
|
||||
data-test-subj="workspace-apply-changes-prompt"
|
||||
size="s"
|
||||
>
|
||||
<img
|
||||
aria-hidden={true}
|
||||
src={IS_DARK_THEME ? applyChangesIllustrationDark : applyChangesIllustrationLight}
|
||||
alt={applyChangesString}
|
||||
className="lnsWorkspacePanel__promptIllustration"
|
||||
/>
|
||||
<h2>
|
||||
<strong>
|
||||
{i18n.translate('xpack.lens.editorFrame.applyChangesWorkspacePrompt', {
|
||||
defaultMessage: 'Apply changes to render visualization',
|
||||
})}
|
||||
</strong>
|
||||
</h2>
|
||||
<p className="lnsWorkspacePanel__actions">
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
className={DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS}
|
||||
iconType="checkInCircleFilled"
|
||||
onClick={() => dispatchLens(applyChanges())}
|
||||
data-test-subj="lnsApplyChanges__workspace"
|
||||
>
|
||||
{applyChangesString}
|
||||
</EuiButtonEmpty>
|
||||
</p>
|
||||
</EuiText>
|
||||
);
|
||||
};
|
||||
|
||||
const renderVisualization = () => {
|
||||
return (
|
||||
<VisualizationWrapper
|
||||
expression={localState.expressionToRender}
|
||||
|
@ -402,7 +456,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
|
||||
const dragDropContext = useContext(DragContext);
|
||||
|
||||
const renderDragDrop = () => {
|
||||
const renderWorkspace = () => {
|
||||
const customWorkspaceRenderer =
|
||||
activeDatasourceId &&
|
||||
datasourceMap[activeDatasourceId]?.getCustomWorkspaceRenderer &&
|
||||
|
@ -413,9 +467,19 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
)
|
||||
: undefined;
|
||||
|
||||
return customWorkspaceRenderer ? (
|
||||
customWorkspaceRenderer()
|
||||
) : (
|
||||
if (customWorkspaceRenderer) {
|
||||
return customWorkspaceRenderer();
|
||||
}
|
||||
|
||||
const hasSomethingToRender = localState.expressionToRender !== null;
|
||||
|
||||
const renderWorkspaceContents = hasSomethingToRender
|
||||
? renderVisualization
|
||||
: !changesApplied
|
||||
? renderApplyChangesPrompt
|
||||
: renderDragDropPrompt;
|
||||
|
||||
return (
|
||||
<DragDrop
|
||||
className={classNames('lnsWorkspacePanel__dragDrop', {
|
||||
'lnsWorkspacePanel__dragDrop--fullscreen': isFullscreen,
|
||||
|
@ -428,7 +492,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
order={dropProps.order}
|
||||
>
|
||||
<EuiPageContentBody className="lnsWorkspacePanelWrapper__pageContentBody">
|
||||
{renderVisualization()}
|
||||
{renderWorkspaceContents()}
|
||||
</EuiPageContentBody>
|
||||
</DragDrop>
|
||||
);
|
||||
|
@ -444,7 +508,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
visualizationMap={visualizationMap}
|
||||
isFullscreen={isFullscreen}
|
||||
>
|
||||
{renderDragDrop()}
|
||||
{renderWorkspace()}
|
||||
</WorkspacePanelWrapper>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -31,6 +31,9 @@
|
|||
|
||||
&.lnsWorkspacePanelWrapper--fullscreen {
|
||||
margin-bottom: 0;
|
||||
.lnsWorkspacePanelWrapper__pageContentBody {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -77,6 +80,10 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: background-color $euiAnimSpeedFast ease-in-out;
|
||||
|
||||
.lnsWorkspacePanel__actions {
|
||||
margin-top: $euiSizeL;
|
||||
}
|
||||
}
|
||||
|
||||
.lnsWorkspacePanelWrapper__toolbar {
|
||||
|
@ -84,6 +91,7 @@
|
|||
|
||||
&.lnsWorkspacePanelWrapper__toolbar--fullscreen {
|
||||
padding: $euiSizeS $euiSizeS 0 $euiSizeS;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
& > .euiFlexItem {
|
||||
|
@ -91,14 +99,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
.lnsDropIllustration__adjustFill {
|
||||
fill: $euiColorFullShade;
|
||||
.lnsWorkspacePanel__promptIllustration {
|
||||
overflow: visible; // Shows arrow animation when it gets out of bounds
|
||||
margin-top: 0;
|
||||
margin-bottom: -$euiSize;
|
||||
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
max-width: 176px;
|
||||
max-height: 176px;
|
||||
}
|
||||
|
||||
.lnsWorkspacePanel__dropIllustration {
|
||||
overflow: visible; // Shows arrow animation when it gets out of bounds
|
||||
margin-top: $euiSizeL;
|
||||
margin-bottom: $euiSizeXXL;
|
||||
// Drop shadow values is a dupe of @euiBottomShadowMedium but used as a filter
|
||||
// Hard-coded px values OK (@cchaos)
|
||||
// sass-lint:disable-block indentation
|
||||
|
@ -108,6 +120,10 @@
|
|||
drop-shadow(0 2px 2px transparentize($euiShadowColor, .8));
|
||||
}
|
||||
|
||||
.lnsDropIllustration__adjustFill {
|
||||
fill: $euiColorFullShade;
|
||||
}
|
||||
|
||||
.lnsDropIllustration__hand {
|
||||
animation: lnsWorkspacePanel__illustrationPulseArrow 5s ease-in-out 0s infinite normal forwards;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
disableAutoApply,
|
||||
selectTriggerApplyChanges,
|
||||
} from '../../../state_management';
|
||||
import { setChangesApplied } from '../../../state_management/lens_slice';
|
||||
import { enableAutoApply, setChangesApplied } from '../../../state_management/lens_slice';
|
||||
|
||||
describe('workspace_panel_wrapper', () => {
|
||||
let mockVisualization: jest.Mocked<Visualization>;
|
||||
|
@ -83,19 +83,7 @@ describe('workspace_panel_wrapper', () => {
|
|||
}
|
||||
|
||||
private get applyChangesButton() {
|
||||
return this._instance.find('button[data-test-subj="lensApplyChanges"]');
|
||||
}
|
||||
|
||||
private get autoApplyToggleSwitch() {
|
||||
return this._instance.find('button[data-test-subj="lensToggleAutoApply"]');
|
||||
}
|
||||
|
||||
toggleAutoApply() {
|
||||
this.autoApplyToggleSwitch.simulate('click');
|
||||
}
|
||||
|
||||
public get autoApplySwitchOn() {
|
||||
return this.autoApplyToggleSwitch.prop('aria-checked');
|
||||
return this._instance.find('button[data-test-subj="lnsApplyChanges__toolbar"]');
|
||||
}
|
||||
|
||||
applyChanges() {
|
||||
|
@ -135,28 +123,24 @@ describe('workspace_panel_wrapper', () => {
|
|||
harness = new Harness(instance);
|
||||
});
|
||||
|
||||
it('toggles auto-apply', async () => {
|
||||
it('shows and hides apply-changes button depending on whether auto-apply is enabled', async () => {
|
||||
store.dispatch(disableAutoApply());
|
||||
harness.update();
|
||||
|
||||
expect(selectAutoApplyEnabled(store.getState())).toBeFalsy();
|
||||
expect(harness.autoApplySwitchOn).toBeFalsy();
|
||||
expect(harness.applyChangesExists).toBeTruthy();
|
||||
|
||||
harness.toggleAutoApply();
|
||||
store.dispatch(enableAutoApply());
|
||||
harness.update();
|
||||
|
||||
expect(selectAutoApplyEnabled(store.getState())).toBeTruthy();
|
||||
expect(harness.autoApplySwitchOn).toBeTruthy();
|
||||
expect(harness.applyChangesExists).toBeFalsy();
|
||||
|
||||
harness.toggleAutoApply();
|
||||
store.dispatch(disableAutoApply());
|
||||
harness.update();
|
||||
|
||||
expect(selectAutoApplyEnabled(store.getState())).toBeFalsy();
|
||||
expect(harness.autoApplySwitchOn).toBeFalsy();
|
||||
expect(harness.applyChangesExists).toBeTruthy();
|
||||
});
|
||||
|
||||
it('apply-changes button works', () => {
|
||||
it('apply-changes button applies changes', () => {
|
||||
store.dispatch(disableAutoApply());
|
||||
harness.update();
|
||||
|
||||
|
@ -199,13 +183,11 @@ describe('workspace_panel_wrapper', () => {
|
|||
harness.update();
|
||||
|
||||
expect(harness.applyChangesDisabled).toBeFalsy();
|
||||
expect(harness.autoApplySwitchOn).toBeFalsy();
|
||||
expect(harness.applyChangesExists).toBeTruthy();
|
||||
|
||||
// enable auto apply
|
||||
harness.toggleAutoApply();
|
||||
store.dispatch(enableAutoApply());
|
||||
harness.update();
|
||||
|
||||
expect(harness.autoApplySwitchOn).toBeTruthy();
|
||||
expect(harness.applyChangesExists).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,12 +8,9 @@
|
|||
import './workspace_panel_wrapper.scss';
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { EuiPageContent, EuiFlexGroup, EuiFlexItem, EuiSwitch, EuiButton } from '@elastic/eui';
|
||||
import { EuiPageContent, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { trackUiEvent } from '../../../lens_ui_telemetry';
|
||||
import { Storage } from '../../../../../../../src/plugins/kibana_utils/public';
|
||||
import { DatasourceMap, FramePublicAPI, VisualizationMap } from '../../../types';
|
||||
import { NativeRenderer } from '../../../native_renderer';
|
||||
import { ChartSwitch } from './chart_switch';
|
||||
|
@ -27,13 +24,10 @@ import {
|
|||
useLensSelector,
|
||||
selectChangesApplied,
|
||||
applyChanges,
|
||||
enableAutoApply,
|
||||
disableAutoApply,
|
||||
selectAutoApplyEnabled,
|
||||
} from '../../../state_management';
|
||||
import { WorkspaceTitle } from './title';
|
||||
import { DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from '../config_panel/dimension_container';
|
||||
import { writeToStorage } from '../../../settings_storage';
|
||||
|
||||
export const AUTO_APPLY_DISABLED_STORAGE_KEY = 'autoApplyDisabled';
|
||||
|
||||
|
@ -90,17 +84,6 @@ export function WorkspacePanelWrapper({
|
|||
[dispatchLens]
|
||||
);
|
||||
|
||||
const toggleAutoApply = useCallback(() => {
|
||||
trackUiEvent('toggle_autoapply');
|
||||
|
||||
writeToStorage(
|
||||
new Storage(localStorage),
|
||||
AUTO_APPLY_DISABLED_STORAGE_KEY,
|
||||
String(autoApplyEnabled)
|
||||
);
|
||||
dispatchLens(autoApplyEnabled ? disableAutoApply() : enableAutoApply());
|
||||
}, [dispatchLens, autoApplyEnabled]);
|
||||
|
||||
const warningMessages: React.ReactNode[] = [];
|
||||
if (activeVisualization?.getWarningMessages) {
|
||||
warningMessages.push(
|
||||
|
@ -119,102 +102,82 @@ export function WorkspacePanelWrapper({
|
|||
});
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="none"
|
||||
direction="row"
|
||||
responsive={false}
|
||||
wrap={true}
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiFlexGroup
|
||||
gutterSize="m"
|
||||
direction="row"
|
||||
responsive={false}
|
||||
wrap={true}
|
||||
justifyContent="spaceBetween"
|
||||
className={classNames('lnsWorkspacePanelWrapper__toolbar', {
|
||||
'lnsWorkspacePanelWrapper__toolbar--fullscreen': isFullscreen,
|
||||
})}
|
||||
>
|
||||
{!isFullscreen && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="m" justifyContent="flexStart">
|
||||
<EuiFlexItem grow={false}>
|
||||
<ChartSwitch
|
||||
data-test-subj="lnsChartSwitcher"
|
||||
visualizationMap={visualizationMap}
|
||||
datasourceMap={datasourceMap}
|
||||
framePublicAPI={framePublicAPI}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{activeVisualization && activeVisualization.renderToolbar && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<NativeRenderer
|
||||
render={activeVisualization.renderToolbar}
|
||||
nativeProps={{
|
||||
frame: framePublicAPI,
|
||||
state: visualizationState,
|
||||
setState: setVisualizationState,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexStart"
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
>
|
||||
{!(isFullscreen && (autoApplyEnabled || warningMessages?.length)) && (
|
||||
<div>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="m"
|
||||
direction="row"
|
||||
responsive={false}
|
||||
wrap={true}
|
||||
justifyContent="spaceBetween"
|
||||
className={classNames('lnsWorkspacePanelWrapper__toolbar', {
|
||||
'lnsWorkspacePanelWrapper__toolbar--fullscreen': isFullscreen,
|
||||
})}
|
||||
>
|
||||
{!isFullscreen && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="m" justifyContent="flexStart">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
label={i18n.translate('xpack.lens.editorFrame.autoApply', {
|
||||
defaultMessage: 'Auto-apply',
|
||||
})}
|
||||
checked={autoApplyEnabled}
|
||||
onChange={toggleAutoApply}
|
||||
compressed={true}
|
||||
className={DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS}
|
||||
data-test-subj="lensToggleAutoApply"
|
||||
<ChartSwitch
|
||||
data-test-subj="lnsChartSwitcher"
|
||||
visualizationMap={visualizationMap}
|
||||
datasourceMap={datasourceMap}
|
||||
framePublicAPI={framePublicAPI}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{!autoApplyEnabled && (
|
||||
{activeVisualization && activeVisualization.renderToolbar && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<div>
|
||||
<EuiButton
|
||||
disabled={autoApplyEnabled || changesApplied}
|
||||
fill
|
||||
className={DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS}
|
||||
iconType="play"
|
||||
onClick={() => dispatchLens(applyChanges())}
|
||||
size="s"
|
||||
data-test-subj="lensApplyChanges"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.editorFrame.applyChangesLabel"
|
||||
defaultMessage="Apply"
|
||||
/>
|
||||
</EuiButton>
|
||||
</div>
|
||||
<NativeRenderer
|
||||
render={activeVisualization.renderToolbar}
|
||||
nativeProps={{
|
||||
frame: framePublicAPI,
|
||||
state: visualizationState,
|
||||
setState: setVisualizationState,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{warningMessages && warningMessages.length ? (
|
||||
<WarningsPopover>{warningMessages}</WarningsPopover>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
gutterSize="s"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
{warningMessages?.length ? (
|
||||
<WarningsPopover>{warningMessages}</WarningsPopover>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
{!autoApplyEnabled && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<div>
|
||||
<EuiButton
|
||||
disabled={autoApplyEnabled || changesApplied}
|
||||
fill
|
||||
className={DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS}
|
||||
iconType="checkInCircleFilled"
|
||||
onClick={() => dispatchLens(applyChanges())}
|
||||
size="m"
|
||||
data-test-subj="lnsApplyChanges__toolbar"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.editorFrame.applyChangesLabel"
|
||||
defaultMessage="Apply changes"
|
||||
/>
|
||||
</EuiButton>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<EuiPageContent
|
||||
className={classNames('lnsWorkspacePanelWrapper', {
|
||||
|
|
|
@ -42,11 +42,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.lens.disableAutoApply();
|
||||
|
||||
expect(await PageObjects.lens.getAutoApplyEnabled()).not.to.be.ok();
|
||||
|
||||
await PageObjects.lens.closeSettingsMenu();
|
||||
});
|
||||
|
||||
it('should preserve auto-apply controls with full-screen datasource', async () => {
|
||||
it('should preserve apply-changes button with full-screen datasource', async () => {
|
||||
await PageObjects.lens.goToTimeRange();
|
||||
|
||||
await PageObjects.lens.disableAutoApply();
|
||||
|
||||
await PageObjects.lens.configureDimension({
|
||||
dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension',
|
||||
operation: 'formula',
|
||||
|
@ -56,7 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
PageObjects.lens.toggleFullscreen();
|
||||
|
||||
expect(await PageObjects.lens.getAutoApplyToggleExists()).to.be.ok();
|
||||
expect(await PageObjects.lens.applyChangesExists('toolbar')).to.be.ok();
|
||||
|
||||
PageObjects.lens.toggleFullscreen();
|
||||
|
||||
|
@ -79,9 +83,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
// assert that changes haven't been applied
|
||||
await PageObjects.lens.waitForEmptyWorkspace();
|
||||
await PageObjects.lens.waitForWorkspaceWithApplyChangesPrompt();
|
||||
|
||||
await PageObjects.lens.applyChanges();
|
||||
await PageObjects.lens.applyChanges('workspace');
|
||||
|
||||
await PageObjects.lens.waitForVisualization('xyVisChart');
|
||||
});
|
||||
|
@ -89,11 +93,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
it('should hide suggestions when a change is made', async () => {
|
||||
await PageObjects.lens.switchToVisualization('lnsDatatable');
|
||||
|
||||
expect(await PageObjects.lens.getAreSuggestionsPromptingToApply()).to.be.ok();
|
||||
expect(await PageObjects.lens.applyChangesExists('suggestions')).to.be.ok();
|
||||
|
||||
await PageObjects.lens.applyChanges(true);
|
||||
await PageObjects.lens.applyChanges('suggestions');
|
||||
|
||||
expect(await PageObjects.lens.getAreSuggestionsPromptingToApply()).not.to.be.ok();
|
||||
expect(await PageObjects.lens.applyChangesExists('suggestions')).not.to.be.ok();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
expect(await PageObjects.lens.getLayerCount()).to.eql(2);
|
||||
await PageObjects.lens.removeLayer();
|
||||
await PageObjects.lens.removeLayer();
|
||||
await testSubjects.existOrFail('empty-workspace');
|
||||
await testSubjects.existOrFail('workspace-drag-drop-prompt');
|
||||
});
|
||||
|
||||
it('should edit settings of xy line chart', async () => {
|
||||
|
|
|
@ -276,7 +276,13 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
|
|||
|
||||
async waitForEmptyWorkspace() {
|
||||
await retry.try(async () => {
|
||||
await testSubjects.existOrFail(`empty-workspace`);
|
||||
await testSubjects.existOrFail(`workspace-drag-drop-prompt`);
|
||||
});
|
||||
},
|
||||
|
||||
async waitForWorkspaceWithApplyChangesPrompt() {
|
||||
await retry.try(async () => {
|
||||
await testSubjects.existOrFail(`workspace-apply-changes-prompt`);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -1336,32 +1342,48 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
|
|||
return testSubjects.exists('lnsEmptySizeRatioButtonGroup');
|
||||
},
|
||||
|
||||
getAutoApplyToggleExists() {
|
||||
return testSubjects.exists('lensToggleAutoApply');
|
||||
settingsMenuOpen() {
|
||||
return testSubjects.exists('lnsApp__settingsMenu');
|
||||
},
|
||||
|
||||
enableAutoApply() {
|
||||
return testSubjects.setEuiSwitch('lensToggleAutoApply', 'check');
|
||||
async openSettingsMenu() {
|
||||
if (await this.settingsMenuOpen()) return;
|
||||
|
||||
await testSubjects.click('lnsApp_settingsButton');
|
||||
},
|
||||
|
||||
disableAutoApply() {
|
||||
return testSubjects.setEuiSwitch('lensToggleAutoApply', 'uncheck');
|
||||
async closeSettingsMenu() {
|
||||
if (!(await this.settingsMenuOpen())) return;
|
||||
|
||||
await testSubjects.click('lnsApp_settingsButton');
|
||||
},
|
||||
|
||||
getAutoApplyEnabled() {
|
||||
return testSubjects.isEuiSwitchChecked('lensToggleAutoApply');
|
||||
async enableAutoApply() {
|
||||
await this.openSettingsMenu();
|
||||
|
||||
return testSubjects.setEuiSwitch('lnsToggleAutoApply', 'check');
|
||||
},
|
||||
|
||||
async applyChanges(throughSuggestions = false) {
|
||||
const applyButtonSelector = throughSuggestions
|
||||
? 'lnsSuggestionApplyChanges'
|
||||
: 'lensApplyChanges';
|
||||
async disableAutoApply() {
|
||||
await this.openSettingsMenu();
|
||||
|
||||
return testSubjects.setEuiSwitch('lnsToggleAutoApply', 'uncheck');
|
||||
},
|
||||
|
||||
async getAutoApplyEnabled() {
|
||||
await this.openSettingsMenu();
|
||||
|
||||
return testSubjects.isEuiSwitchChecked('lnsToggleAutoApply');
|
||||
},
|
||||
|
||||
applyChangesExists(whichButton: 'toolbar' | 'suggestions' | 'workspace') {
|
||||
return testSubjects.exists(`lnsApplyChanges__${whichButton}`);
|
||||
},
|
||||
|
||||
async applyChanges(whichButton: 'toolbar' | 'suggestions' | 'workspace') {
|
||||
const applyButtonSelector = `lnsApplyChanges__${whichButton}`;
|
||||
await testSubjects.waitForEnabled(applyButtonSelector);
|
||||
await testSubjects.click(applyButtonSelector);
|
||||
},
|
||||
|
||||
async getAreSuggestionsPromptingToApply() {
|
||||
return testSubjects.exists('lnsSuggestionApplyChanges');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue