mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Text based] Enables save Lens chart to dashboard from Discover (#159190)
## Summary Adds a save to dashboard functionality in the Lens charts created in Discover by text based languages. <img width="1738" alt="image" src="c4b5f459
-1124-4800-954f-298332601eaf"> <img width="832" alt="image" src="ae742d0a
-5911-4622-8387-60db87daffcc"> We allow only saving by value panels and not by reference because we are going to remove this functionality in the next minor (create Lens text based languages SOs). ### 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) - [ ] [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/))
This commit is contained in:
parent
af3f13eaf9
commit
5c7753fc26
8 changed files with 134 additions and 14 deletions
|
@ -46,4 +46,9 @@ export const unifiedHistogramServicesMock = {
|
|||
clear: jest.fn(),
|
||||
},
|
||||
expressions: expressionsPluginMock.createStartContract(),
|
||||
capabilities: {
|
||||
dashboard: {
|
||||
showWriteControls: true,
|
||||
},
|
||||
},
|
||||
} as unknown as UnifiedHistogramServices;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import React, { ReactElement } from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import type { Capabilities } from '@kbn/core/public';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import type { Suggestion } from '@kbn/lens-plugin/public';
|
||||
import type { UnifiedHistogramFetchStatus } from '../types';
|
||||
|
@ -41,6 +42,7 @@ async function mountComponent({
|
|||
currentSuggestion,
|
||||
allSuggestions,
|
||||
isPlainRecord,
|
||||
hasDashboardPermissions,
|
||||
}: {
|
||||
noChart?: boolean;
|
||||
noHits?: boolean;
|
||||
|
@ -51,11 +53,21 @@ async function mountComponent({
|
|||
currentSuggestion?: Suggestion;
|
||||
allSuggestions?: Suggestion[];
|
||||
isPlainRecord?: boolean;
|
||||
hasDashboardPermissions?: boolean;
|
||||
} = {}) {
|
||||
(searchSourceInstanceMock.fetch$ as jest.Mock).mockImplementation(
|
||||
jest.fn().mockReturnValue(of({ rawResponse: { hits: { total: noHits ? 0 : 2 } } }))
|
||||
);
|
||||
|
||||
const services = {
|
||||
...unifiedHistogramServicesMock,
|
||||
capabilities: {
|
||||
dashboard: {
|
||||
showWriteControls: hasDashboardPermissions ?? true,
|
||||
},
|
||||
} as unknown as Capabilities,
|
||||
};
|
||||
|
||||
const props = {
|
||||
dataView,
|
||||
query: {
|
||||
|
@ -64,7 +76,7 @@ async function mountComponent({
|
|||
},
|
||||
filters: [],
|
||||
timeRange: { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' },
|
||||
services: unifiedHistogramServicesMock,
|
||||
services,
|
||||
hits: noHits
|
||||
? undefined
|
||||
: {
|
||||
|
@ -221,6 +233,27 @@ describe('Chart', () => {
|
|||
expect(component.find(SuggestionSelector).exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render the save button when chart is visible and suggestions exist', async () => {
|
||||
const component = await mountComponent({
|
||||
currentSuggestion: currentSuggestionMock,
|
||||
allSuggestions: allSuggestionsMock,
|
||||
});
|
||||
expect(
|
||||
component.find('[data-test-subj="unifiedHistogramSaveVisualization"]').exists()
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not render the save button when the dashboard save by value permissions are false', async () => {
|
||||
const component = await mountComponent({
|
||||
currentSuggestion: currentSuggestionMock,
|
||||
allSuggestions: allSuggestionsMock,
|
||||
hasDashboardPermissions: false,
|
||||
});
|
||||
expect(
|
||||
component.find('[data-test-subj="unifiedHistogramSaveVisualization"]').exists()
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not render the Lens SuggestionsSelector when chart is hidden', async () => {
|
||||
const component = await mountComponent({
|
||||
chartHidden: true,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ReactElement, useMemo } from 'react';
|
||||
import { ReactElement, useMemo, useState } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
|
@ -111,6 +111,7 @@ export function Chart({
|
|||
onFilter,
|
||||
onBrushEnd,
|
||||
}: ChartProps) {
|
||||
const [isSaveModalVisible, setIsSaveModalVisible] = useState(false);
|
||||
const {
|
||||
showChartOptionsPopover,
|
||||
chartRef,
|
||||
|
@ -221,6 +222,9 @@ export function Chart({
|
|||
lensAttributes: lensAttributesContext.attributes,
|
||||
isPlainRecord,
|
||||
});
|
||||
const LensSaveModalComponent = services.lens.SaveModalComponent;
|
||||
const canSaveVisualization =
|
||||
chartVisible && currentSuggestion && services.capabilities.dashboard?.showWriteControls;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
|
@ -271,6 +275,27 @@ export function Chart({
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{canSaveVisualization && (
|
||||
<>
|
||||
<EuiFlexItem grow={false} css={chartToolButtonCss}>
|
||||
<EuiToolTip
|
||||
content={i18n.translate('unifiedHistogram.saveVisualizationButton', {
|
||||
defaultMessage: 'Save visualization',
|
||||
})}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
size="xs"
|
||||
iconType="save"
|
||||
onClick={() => setIsSaveModalVisible(true)}
|
||||
data-test-subj="unifiedHistogramSaveVisualization"
|
||||
aria-label={i18n.translate('unifiedHistogram.saveVisualizationButton', {
|
||||
defaultMessage: 'Save visualization',
|
||||
})}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
{onEditVisualization && (
|
||||
<EuiFlexItem grow={false} css={chartToolButtonCss}>
|
||||
<EuiToolTip
|
||||
|
@ -354,6 +379,14 @@ export function Chart({
|
|||
{appendHistogram}
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{canSaveVisualization && isSaveModalVisible && lensAttributesContext.attributes && (
|
||||
<LensSaveModalComponent
|
||||
initialInput={lensAttributesContext.attributes as unknown as LensEmbeddableInput}
|
||||
onSave={() => {}}
|
||||
onClose={() => setIsSaveModalVisible(false)}
|
||||
isSaveable={false}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -739,4 +739,18 @@ describe('getLensAttributes', () => {
|
|||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should return suggestion title if no title is given', () => {
|
||||
expect(
|
||||
getLensAttributes({
|
||||
title: undefined,
|
||||
filters,
|
||||
query,
|
||||
dataView,
|
||||
timeInterval,
|
||||
breakdownField: undefined,
|
||||
suggestion: currentSuggestionMock,
|
||||
}).attributes.title
|
||||
).toBe(currentSuggestionMock.title);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -184,6 +184,7 @@ export const getLensAttributes = ({
|
|||
const attributes = {
|
||||
title:
|
||||
title ??
|
||||
suggestion?.title ??
|
||||
i18n.translate('unifiedHistogram.lensTitle', {
|
||||
defaultMessage: 'Edit visualization',
|
||||
}),
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import type { Theme } from '@kbn/charts-plugin/public/plugin';
|
||||
import type { IUiSettingsClient } from '@kbn/core/public';
|
||||
import type { IUiSettingsClient, Capabilities } from '@kbn/core/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
import type { LensPublicStart } from '@kbn/lens-plugin/public';
|
||||
|
@ -42,6 +42,7 @@ export interface UnifiedHistogramServices {
|
|||
lens: LensPublicStart;
|
||||
storage: Storage;
|
||||
expressions: ExpressionsStart;
|
||||
capabilities: Capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -184,5 +184,21 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
return dimensions.length === 2 && (await dimensions[1].getVisibleText()) === 'average';
|
||||
});
|
||||
});
|
||||
|
||||
it('should save correctly chart to dashboard', async () => {
|
||||
await PageObjects.discover.selectTextBaseLang('SQL');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await monacoEditor.setCodeEditorValue(
|
||||
'SELECT extension, AVG("bytes") as average FROM "logstash*" GROUP BY extension'
|
||||
);
|
||||
await testSubjects.click('querySubmitButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testSubjects.click('TextBasedLangEditor-expand');
|
||||
await testSubjects.click('unifiedHistogramSaveVisualization');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
await PageObjects.lens.saveModal('TextBasedChart', false, false, false, 'new');
|
||||
await testSubjects.existOrFail('embeddablePanelHeading-TextBasedChart');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -709,6 +709,30 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
|
|||
await testSubjects.missingOrFail(`lns-fieldOption-${field}`);
|
||||
}
|
||||
},
|
||||
async saveModal(
|
||||
title: string,
|
||||
saveAsNew?: boolean,
|
||||
redirectToOrigin?: boolean,
|
||||
saveToLibrary?: boolean,
|
||||
addToDashboard?: 'new' | 'existing' | null,
|
||||
dashboardId?: string
|
||||
) {
|
||||
await PageObjects.timeToVisualize.setSaveModalValues(title, {
|
||||
saveAsNew,
|
||||
redirectToOrigin,
|
||||
addToDashboard: addToDashboard ? addToDashboard : null,
|
||||
dashboardId,
|
||||
saveToLibrary,
|
||||
});
|
||||
|
||||
await testSubjects.click('confirmSaveSavedObjectButton');
|
||||
await retry.waitForWithTimeout('Save modal to disappear', 1000, () =>
|
||||
testSubjects
|
||||
.missingOrFail('confirmSaveSavedObjectButton')
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the current Lens visualization.
|
||||
|
@ -724,20 +748,13 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testSubjects.click('lnsApp_saveButton');
|
||||
|
||||
await PageObjects.timeToVisualize.setSaveModalValues(title, {
|
||||
await this.saveModal(
|
||||
title,
|
||||
saveAsNew,
|
||||
redirectToOrigin,
|
||||
addToDashboard: addToDashboard ? addToDashboard : null,
|
||||
dashboardId,
|
||||
saveToLibrary,
|
||||
});
|
||||
|
||||
await testSubjects.click('confirmSaveSavedObjectButton');
|
||||
await retry.waitForWithTimeout('Save modal to disappear', 1000, () =>
|
||||
testSubjects
|
||||
.missingOrFail('confirmSaveSavedObjectButton')
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
addToDashboard,
|
||||
dashboardId
|
||||
);
|
||||
},
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue