[7.12] [Time to Visualize] Add Discrete Library Option to Save Modal (#94589) (#94962)

* [Time to Visualize] Add Discrete Library Option to Save Modal (#94589)

* save modal UI and Redirect and save to library

Co-authored-by: Poff Poffenberger <poffdeluxe@gmail.com>
# Conflicts:
#	.github/CODEOWNERS
#	x-pack/test/functional/apps/lens/add_to_dashboard.ts

* Update x-pack/test/functional/apps/lens/add_to_dashboard.ts

* Delete CODEOWNERS
This commit is contained in:
Devon Thomson 2021-03-18 18:34:31 -04:00 committed by GitHub
parent a1fdc06bd0
commit 15bd4b1889
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 636 additions and 164 deletions

View file

@ -30,7 +30,7 @@ export interface SaveModalDashboardProps {
documentInfo: SaveModalDocumentInfo;
objectType: string;
onClose: () => void;
onSave: (props: OnSaveProps & { dashboardId: string | null }) => void;
onSave: (props: OnSaveProps & { dashboardId: string | null; addToLibrary: boolean }) => void;
tagOptions?: React.ReactNode | ((state: SaveModalState) => React.ReactNode);
}
@ -48,6 +48,9 @@ export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) {
const [dashboardOption, setDashboardOption] = useState<'new' | 'existing' | null>(
documentId || disableDashboardOptions ? null : 'existing'
);
const [isAddToLibrarySelected, setAddToLibrary] = useState<boolean>(
!initialCopyOnSave || disableDashboardOptions
);
const [selectedDashboard, setSelectedDashboard] = useState<{ id: string; name: string } | null>(
null
);
@ -62,12 +65,13 @@ export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) {
onChange={(option) => {
setDashboardOption(option);
}}
{...{ copyOnSave, documentId, dashboardOption }}
{...{ copyOnSave, documentId, dashboardOption, setAddToLibrary, isAddToLibrarySelected }}
/>
)
: null;
const onCopyOnSaveChange = (newCopyOnSave: boolean) => {
setAddToLibrary(true);
setDashboardOption(null);
setCopyOnSave(newCopyOnSave);
};
@ -85,7 +89,7 @@ export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) {
}
}
props.onSave({ ...onSaveProps, dashboardId });
props.onSave({ ...onSaveProps, dashboardId, addToLibrary: isAddToLibrarySelected });
};
const saveLibraryLabel =
@ -113,7 +117,7 @@ export function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) {
onSave={onModalSave}
title={documentInfo.title}
showCopyOnSave={documentId ? true : false}
options={dashboardOption === null ? tagOptions : undefined} // Show tags when not adding to dashboard
options={isAddToLibrarySelected ? tagOptions : undefined} // Show tags when not adding to dashboard
description={documentInfo.description}
showDescription={true}
{...{

View file

@ -44,6 +44,7 @@ export function Example({
hasDocumentId: boolean;
} & StorybookParams) {
const [dashboardOption, setDashboardOption] = useState<'new' | 'existing' | null>('existing');
const [isAddToLibrarySelected, setAddToLibrary] = useState(false);
return (
<SaveModalDashboardSelector
@ -52,6 +53,8 @@ export function Example({
dashboardOption={dashboardOption}
copyOnSave={copyOnSave}
documentId={hasDocumentId ? 'abc' : undefined}
isAddToLibrarySelected={isAddToLibrarySelected}
setAddToLibrary={setAddToLibrary}
/>
);
}

View file

@ -19,6 +19,7 @@ import {
EuiIconTip,
EuiPanel,
EuiSpacer,
EuiCheckbox,
} from '@elastic/eui';
import { DashboardPicker, DashboardPickerProps } from './dashboard_picker';
@ -29,24 +30,105 @@ export interface SaveModalDashboardSelectorProps {
copyOnSave: boolean;
documentId?: string;
onSelectDashboard: DashboardPickerProps['onChange'];
setAddToLibrary: (selected: boolean) => void;
isAddToLibrarySelected: boolean;
dashboardOption: 'new' | 'existing' | null;
onChange: (dashboardOption: 'new' | 'existing' | null) => void;
}
export function SaveModalDashboardSelector(props: SaveModalDashboardSelectorProps) {
const { documentId, onSelectDashboard, dashboardOption, onChange, copyOnSave } = props;
const {
documentId,
onSelectDashboard,
setAddToLibrary,
isAddToLibrarySelected,
dashboardOption,
onChange,
copyOnSave,
} = props;
const isDisabled = !copyOnSave && !!documentId;
return (
<>
<EuiFormRow
label={
<FormattedMessage
id="presentationUtil.saveModalDashboard.addToDashboardLabel"
defaultMessage="Add to dashboard"
/>
}
hasChildLabel={false}
>
<>
<EuiPanel color="subdued" hasShadow={false} data-test-subj="add-to-dashboard-options">
<div>
<>
<EuiRadio
checked={dashboardOption === 'existing'}
id="existing-dashboard-option"
name="dashboard-option"
label={i18n.translate(
'presentationUtil.saveModalDashboard.existingDashboardOptionLabel',
{
defaultMessage: 'Existing',
}
)}
onChange={() => onChange('existing')}
disabled={isDisabled}
/>
<div className="savAddDashboard__searchDashboards">
<DashboardPicker
isDisabled={dashboardOption !== 'existing'}
onChange={onSelectDashboard}
/>
</div>
<EuiSpacer size="s" />
</>
<>
<EuiRadio
checked={dashboardOption === 'new'}
id="new-dashboard-option"
name="dashboard-option"
label={i18n.translate(
'presentationUtil.saveModalDashboard.newDashboardOptionLabel',
{
defaultMessage: 'New',
}
)}
onChange={() => onChange('new')}
disabled={isDisabled}
/>
<EuiSpacer size="s" />
</>
<EuiRadio
checked={dashboardOption === null}
id="add-to-library-option"
name="dashboard-option"
label={i18n.translate(
'presentationUtil.saveModalDashboard.noDashboardOptionLabel',
{
defaultMessage: 'None',
}
)}
onChange={() => {
setAddToLibrary(true);
onChange(null);
}}
disabled={isDisabled}
/>
</div>
</EuiPanel>
<EuiSpacer size="s" />
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
<EuiFlexItem grow={false}>
<FormattedMessage
id="presentationUtil.saveModalDashboard.addToDashboardLabel"
defaultMessage="Add to dashboard"
<EuiFlexItem grow={false} data-test-subj="add-to-library-checkbox">
<EuiCheckbox
id="add-to-library-checkbox"
label={i18n.translate('presentationUtil.saveModalDashboard.libraryOptionLabel', {
defaultMessage: 'Add to library',
})}
checked={isAddToLibrarySelected}
disabled={dashboardOption === null || isDisabled}
onChange={(event) => setAddToLibrary(event.target.checked)}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
@ -55,67 +137,13 @@ export function SaveModalDashboardSelector(props: SaveModalDashboardSelectorProp
content={
<FormattedMessage
id="presentationUtil.saveModalDashboard.dashboardInfoTooltip"
defaultMessage="Items added to a dashboard will not appear in the library and must be edited from the dashboard."
defaultMessage="items added to the Visualize Library are available to all dashboards. Edits to a library item appear everywhere it is used."
/>
}
/>
</EuiFlexItem>
</EuiFlexGroup>
}
hasChildLabel={false}
>
<EuiPanel color="subdued" hasShadow={false} data-test-subj="add-to-dashboard-options">
<div>
<>
<EuiRadio
checked={dashboardOption === 'existing'}
id="existing-dashboard-option"
name="dashboard-option"
label={i18n.translate(
'presentationUtil.saveModalDashboard.existingDashboardOptionLabel',
{
defaultMessage: 'Existing',
}
)}
onChange={() => onChange('existing')}
disabled={isDisabled}
/>
<div className="savAddDashboard__searchDashboards">
<DashboardPicker
isDisabled={dashboardOption !== 'existing'}
onChange={onSelectDashboard}
/>
</div>
<EuiSpacer size="s" />
</>
<>
<EuiRadio
checked={dashboardOption === 'new'}
id="new-dashboard-option"
name="dashboard-option"
label={i18n.translate(
'presentationUtil.saveModalDashboard.newDashboardOptionLabel',
{
defaultMessage: 'New',
}
)}
onChange={() => onChange('new')}
disabled={isDisabled}
/>
<EuiSpacer size="s" />
</>
<EuiRadio
checked={dashboardOption === null}
id="add-to-library-option"
name="dashboard-option"
label={i18n.translate('presentationUtil.saveModalDashboard.libraryOptionLabel', {
defaultMessage: 'No dashboard, but add to library',
})}
onChange={() => onChange(null)}
disabled={isDisabled}
/>
</div>
</EuiPanel>
</>
</EuiFormRow>
</>
);

View file

@ -93,7 +93,7 @@ export const getTopNavConfig = (
/**
* Called when the user clicks "Save" button.
*/
async function doSave(saveOptions: SavedObjectSaveOpts) {
async function doSave(saveOptions: SavedObjectSaveOpts & { dashboardId?: string }) {
const newlyCreated = !Boolean(savedVis.id) || savedVis.copyOnSave;
// vis.title was not bound and it's needed to reflect title into visState
stateContainer.transitions.setVis({
@ -118,7 +118,7 @@ export const getTopNavConfig = (
'data-test-subj': 'saveVisualizationSuccess',
});
if (originatingApp && saveOptions.returnToOrigin) {
if ((originatingApp && saveOptions.returnToOrigin) || saveOptions.dashboardId) {
if (!embeddableId) {
const appPath = `${VisualizeConstants.EDIT_PATH}/${encodeURIComponent(id)}`;
@ -127,16 +127,26 @@ export const getTopNavConfig = (
setActiveUrl(appPath);
}
const app = originatingApp || 'dashboards';
let path;
if (saveOptions.dashboardId) {
path =
saveOptions.dashboardId === 'new' ? '#/create' : `#/view/${saveOptions.dashboardId}`;
}
if (newlyCreated && stateTransfer) {
stateTransfer.navigateToWithEmbeddablePackage(originatingApp, {
stateTransfer.navigateToWithEmbeddablePackage(app, {
state: {
type: VISUALIZE_EMBEDDABLE_TYPE,
input: { savedObjectId: id },
embeddableId,
},
path,
});
} else {
application.navigateToApp(originatingApp);
// TODO: need the same thing here?
application.navigateToApp(app, { path });
}
} else {
if (setOriginatingApp && originatingApp && newlyCreated) {
@ -321,7 +331,11 @@ export const getTopNavConfig = (
newDescription,
returnToOrigin,
dashboardId,
}: OnSaveProps & { returnToOrigin?: boolean } & { dashboardId?: string | null }) => {
addToLibrary,
}: OnSaveProps & { returnToOrigin?: boolean } & {
dashboardId?: string | null;
addToLibrary?: boolean;
}) => {
const currentTitle = savedVis.title;
savedVis.title = newTitle;
embeddableHandler.updateInput({ title: newTitle });
@ -337,9 +351,12 @@ export const getTopNavConfig = (
isTitleDuplicateConfirmed,
onTitleDuplicate,
returnToOrigin,
dashboardId: !!dashboardId ? dashboardId : undefined,
};
if (dashboardId) {
// If we're adding to a dashboard and not saving to library,
// we'll want to use a by-value operation
if (dashboardId && !addToLibrary) {
const appPath = `${VisualizeConstants.LANDING_PAGE_PATH}`;
// Manually insert a new url so the back button will open the saved visualization.
@ -369,6 +386,8 @@ export const getTopNavConfig = (
return { id: true };
}
// We're adding the viz to a library so we need to save it and then
// add to a dashboard if necessary
const response = await doSave(saveOptions);
// If the save wasn't successful, put the original values back.
if (!response.id || response.error) {

View file

@ -26,7 +26,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
]);
describe('Add to Dashboard', function describeIndexTests() {
it('adding a new metric to a new dashboard', async function () {
it('adding a new metric to a new dashboard by value', async function () {
await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickMetric();
await PageObjects.visualize.clickNewSearch();
@ -36,6 +36,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.timeToVisualize.saveFromModal('My New Vis 1', {
addToDashboard: 'new',
saveToLibrary: false,
});
await PageObjects.dashboard.waitForRenderComplete();
@ -43,10 +44,39 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists('My New Vis 1');
expect(isLinked).to.be(false);
await PageObjects.timeToVisualize.resetNewDashboard();
});
it('adding a existing metric to a new dashboard', async function () {
it('adding a new metric to a new dashboard by reference', async function () {
await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickMetric();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
await testSubjects.click('visualizeSaveButton');
await PageObjects.timeToVisualize.saveFromModal('My Saved New Vis 1', {
addToDashboard: 'new',
saveToLibrary: true,
});
await PageObjects.dashboard.waitForRenderComplete();
await dashboardExpect.metricValuesExist(['14,004']);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists(
'My Saved New Vis 1'
);
expect(isLinked).to.be(true);
await PageObjects.timeToVisualize.resetNewDashboard();
});
it('adding a existing metric to a new dashboard by value', async function () {
await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickMetric();
await PageObjects.visualize.clickNewSearch();
@ -57,6 +87,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
// Save this new viz to library
await PageObjects.timeToVisualize.saveFromModal('My New Vis 1', {
addToDashboard: null,
saveToLibrary: true,
});
await testSubjects.click('visualizeSaveButton');
@ -68,6 +99,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.timeToVisualize.saveFromModal('My New Vis 1 Copy', {
addToDashboard: 'new',
saveAsNew: true,
saveToLibrary: false,
});
await PageObjects.dashboard.waitForRenderComplete();
@ -75,10 +107,85 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists(
'My New Vis 1 Copy'
);
expect(isLinked).to.be(false);
await PageObjects.timeToVisualize.resetNewDashboard();
});
it('adding a new metric to an existing dashboard', async function () {
it('adding a existing metric to a new dashboard by reference', async function () {
await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickMetric();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
await testSubjects.click('visualizeSaveButton');
// Save this new viz to library
await PageObjects.timeToVisualize.saveFromModal('Another New Vis 1', {
addToDashboard: null,
saveToLibrary: true,
});
await testSubjects.click('visualizeSaveButton');
// All the options should be disabled
await PageObjects.timeToVisualize.ensureDashboardOptionsAreDisabled();
// Save a new copy of this viz to a new dashboard
await PageObjects.timeToVisualize.saveFromModal('Another New Vis 1 Copy', {
addToDashboard: 'new',
saveAsNew: true,
saveToLibrary: true,
});
await PageObjects.dashboard.waitForRenderComplete();
await dashboardExpect.metricValuesExist(['14,004']);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists(
'Another New Vis 1 Copy'
);
expect(isLinked).to.be(true);
await PageObjects.timeToVisualize.resetNewDashboard();
});
it('adding a new metric to an existing dashboard by value', async function () {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.addVisualizations(['Visualization AreaChart']);
await PageObjects.dashboard.saveDashboard('My Excellent Dashboard');
await PageObjects.dashboard.gotoDashboardLandingPage();
await listingTable.searchAndExpectItemsCount('dashboard', 'My Excellent Dashboard', 1);
await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickMetric();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
await testSubjects.click('visualizeSaveButton');
await PageObjects.timeToVisualize.saveFromModal('My New Vis 2', {
addToDashboard: 'existing',
dashboardId: 'My Excellent Dashboard',
saveToLibrary: false,
});
await PageObjects.dashboard.waitForRenderComplete();
await dashboardExpect.metricValuesExist(['14,004']);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(2);
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists('My New Vis 2');
expect(isLinked).to.be(false);
});
it('adding a new metric to an existing dashboard by reference', async function () {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
@ -94,18 +201,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await testSubjects.click('visualizeSaveButton');
await PageObjects.timeToVisualize.saveFromModal('My New Vis 2', {
await PageObjects.timeToVisualize.saveFromModal('My Saved New Vis 2', {
addToDashboard: 'existing',
dashboardId: 'My Wonderful Dashboard',
saveToLibrary: true,
});
await PageObjects.dashboard.waitForRenderComplete();
await dashboardExpect.metricValuesExist(['14,004']);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(2);
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists(
'My Saved New Vis 2'
);
expect(isLinked).to.be(true);
});
it('adding a existing metric to an existing dashboard', async function () {
it('adding a existing metric to an existing dashboard by value', async function () {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
@ -124,6 +237,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
// Save this new viz to library
await PageObjects.timeToVisualize.saveFromModal('My New Vis 2', {
addToDashboard: null,
saveToLibrary: true,
});
await testSubjects.click('visualizeSaveButton');
@ -136,12 +250,64 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
addToDashboard: 'existing',
dashboardId: 'My Very Cool Dashboard',
saveAsNew: true,
saveToLibrary: false,
});
await PageObjects.dashboard.waitForRenderComplete();
await dashboardExpect.metricValuesExist(['14,004']);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(2);
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists(
'My New Vis 2 Copy'
);
expect(isLinked).to.be(false);
});
it('adding a existing metric to an existing dashboard by reference', async function () {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.addVisualizations(['Visualization AreaChart']);
await PageObjects.dashboard.saveDashboard('My Very Neat Dashboard');
await PageObjects.dashboard.gotoDashboardLandingPage();
await listingTable.searchAndExpectItemsCount('dashboard', 'My Very Neat Dashboard', 1);
await PageObjects.visualize.navigateToNewAggBasedVisualization();
await PageObjects.visualize.clickMetric();
await PageObjects.visualize.clickNewSearch();
await PageObjects.timePicker.setDefaultAbsoluteRange();
await testSubjects.click('visualizeSaveButton');
// Save this new viz to library
await PageObjects.timeToVisualize.saveFromModal('Neat Saved Vis 2', {
addToDashboard: null,
saveToLibrary: true,
});
await testSubjects.click('visualizeSaveButton');
// All the options should be disabled
await PageObjects.timeToVisualize.ensureDashboardOptionsAreDisabled();
// Save a new copy of this viz to an existing dashboard
await PageObjects.timeToVisualize.saveFromModal('Neat Saved Vis 2 Copy', {
addToDashboard: 'existing',
dashboardId: 'My Very Neat Dashboard',
saveAsNew: true,
saveToLibrary: true,
});
await PageObjects.dashboard.waitForRenderComplete();
await dashboardExpect.metricValuesExist(['14,004']);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(2);
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists(
'Neat Saved Vis 2 Copy'
);
expect(isLinked).to.be(true);
});
});
}

View file

@ -10,6 +10,7 @@ import { FtrProviderContext } from '../ftr_provider_context';
interface SaveModalArgs {
addToDashboard?: 'new' | 'existing' | null;
saveToLibrary?: boolean;
dashboardId?: string;
saveAsNew?: boolean;
redirectToOrigin?: boolean;
@ -35,7 +36,9 @@ export function TimeToVisualizePageProvider({ getService, getPageObjects }: FtrP
const dashboardSelector = await testSubjects.find('add-to-dashboard-options');
await dashboardSelector.findByCssSelector(`input[id="new-dashboard-option"]:disabled`);
await dashboardSelector.findByCssSelector(`input[id="existing-dashboard-option"]:disabled`);
await dashboardSelector.findByCssSelector(`input[id="add-to-library-option"]:disabled`);
const librarySelector = await testSubjects.find('add-to-library-checkbox');
await librarySelector.findByCssSelector(`input[id="add-to-library-checkbox"]:disabled`);
}
public async resetNewDashboard() {
@ -46,7 +49,13 @@ export function TimeToVisualizePageProvider({ getService, getPageObjects }: FtrP
public async setSaveModalValues(
vizName: string,
{ saveAsNew, redirectToOrigin, addToDashboard, dashboardId }: SaveModalArgs = {}
{
saveAsNew,
redirectToOrigin,
addToDashboard,
dashboardId,
saveToLibrary,
}: SaveModalArgs = {}
) {
await testSubjects.setValue('savedObjectTitle', vizName);
@ -57,13 +66,6 @@ export function TimeToVisualizePageProvider({ getService, getPageObjects }: FtrP
await testSubjects.setEuiSwitch('saveAsNewCheckbox', state);
}
const hasRedirectToOrigin = await testSubjects.exists('returnToOriginModeSwitch');
if (hasRedirectToOrigin && redirectToOrigin !== undefined) {
const state = redirectToOrigin ? 'check' : 'uncheck';
log.debug('redirect to origin checkbox exists. Setting its state to', state);
await testSubjects.setEuiSwitch('returnToOriginModeSwitch', state);
}
const hasDashboardSelector = await testSubjects.exists('add-to-dashboard-options');
if (hasDashboardSelector && addToDashboard !== undefined) {
let option: DashboardPickerOption = 'add-to-library-option';
@ -80,6 +82,40 @@ export function TimeToVisualizePageProvider({ getService, getPageObjects }: FtrP
await find.clickByButtonText(dashboardId);
}
}
const hasSaveToLibrary = await testSubjects.exists('add-to-library-checkbox');
if (hasSaveToLibrary && saveToLibrary !== undefined) {
const libraryCheckbox = await find.byCssSelector('#add-to-library-checkbox');
const isChecked = await libraryCheckbox.isSelected();
const needsClick = isChecked !== saveToLibrary;
const state = saveToLibrary ? 'check' : 'uncheck';
log.debug('save to library checkbox exists. Setting its state to', state);
if (needsClick) {
const selector = await testSubjects.find('add-to-library-checkbox');
const label = await selector.findByCssSelector(`label[for="add-to-library-checkbox"]`);
await label.click();
}
}
const hasRedirectToOrigin = await testSubjects.exists('returnToOriginModeSwitch');
if (hasRedirectToOrigin && redirectToOrigin !== undefined) {
const state = redirectToOrigin ? 'check' : 'uncheck';
log.debug('redirect to origin checkbox exists. Setting its state to', state);
await testSubjects.setEuiSwitch('returnToOriginModeSwitch', state);
}
}
public async libraryNotificationExists(panelTitle: string) {
log.debug('searching for library modal on panel:', panelTitle);
const panel = await testSubjects.find(
`embeddablePanelHeading-${panelTitle.replace(/ /g, '')}`
);
const libraryActionExists = await testSubjects.descendantExists(
'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION',
panel
);
return libraryActionExists;
}
public async saveFromModal(

View file

@ -86,7 +86,7 @@ export const SaveModal = (props: Props) => {
savedObjectsTagging={savedObjectsTagging}
initialTags={tagsIds}
onSave={(saveProps) => {
const saveToLibrary = saveProps.dashboardId === null;
const saveToLibrary = Boolean(saveProps.addToLibrary);
onSave(saveProps, { saveToLibrary });
}}
onClose={onClose}

View file

@ -16,6 +16,7 @@ import { SavedObjectTaggingPluginStart } from '../../../saved_objects_tagging/pu
export type DashboardSaveProps = OnSaveProps & {
returnToOrigin: boolean;
dashboardId?: string | null;
addToLibrary?: boolean;
newTags?: string[];
};

View file

@ -145,7 +145,11 @@ export function getTopNavConfig({
const saveModalProps = {
onSave: async (
props: OnSaveProps & { returnToOrigin?: boolean; dashboardId?: string | null }
props: OnSaveProps & {
returnToOrigin?: boolean;
dashboardId?: string | null;
addToLibrary?: boolean;
}
) => {
try {
await checkForDuplicateTitle(
@ -172,7 +176,7 @@ export function getTopNavConfig({
await savedMap.save({
...props,
newTags: selectedTags,
saveByReference: !props.dashboardId,
saveByReference: Boolean(props.addToLibrary),
});
// showSaveModal wrapper requires onSave to return an object with an id to close the modal after successful save
return { id: 'id' };

View file

@ -23,28 +23,57 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const security = getService('security');
const createNewLens = async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('lens');
await PageObjects.lens.goToTimeRange();
await PageObjects.lens.configureDimension({
dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension',
operation: 'avg',
field: 'bytes',
});
await PageObjects.lens.switchToVisualization('lnsMetric');
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.lens.assertMetric('Average of bytes', '5,727.322');
};
const createAndSaveDashboard = async (dashboardName: string) => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await dashboardAddPanel.clickOpenAddPanel();
await dashboardAddPanel.filterEmbeddableNames('lnsXYvis');
await find.clickByButtonText('lnsXYvis');
await dashboardAddPanel.closeAddPanel();
await PageObjects.lens.goToTimeRange();
await PageObjects.dashboard.saveDashboard(dashboardName);
await PageObjects.dashboard.gotoDashboardLandingPage();
await listingTable.searchAndExpectItemsCount('dashboard', dashboardName, 1);
};
const loadExistingLens = async () => {
await PageObjects.visualize.gotoVisualizationLandingPage();
await listingTable.searchForItemWithName('Artistpreviouslyknownaslens');
await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens');
await PageObjects.lens.goToTimeRange();
await PageObjects.lens.assertMetric('Maximum of bytes', '19,986');
};
describe('lens add-to-dashboards tests', () => {
it('should allow new lens vizs be added to a new dashboard', async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('lens');
await PageObjects.lens.goToTimeRange();
await PageObjects.lens.configureDimension({
dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension',
operation: 'avg',
field: 'bytes',
});
await PageObjects.lens.switchToVisualization('lnsMetric');
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.lens.assertMetric('Average of bytes', '5,727.322');
await PageObjects.lens.save('New Lens from Modal', false, false, 'new');
it('should allow new lens to be added by value to a new dashboard', async () => {
await createNewLens();
await PageObjects.lens.save('New Lens from Modal', false, false, false, 'new');
await PageObjects.dashboard.waitForRenderComplete();
await PageObjects.lens.assertMetric('Average of bytes', '5,727.322');
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists(
'New Lens from Modal'
);
expect(isLinked).to.be(false);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
@ -52,18 +81,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.timeToVisualize.resetNewDashboard();
});
it('should allow existing lens vizs be added to a new dashboard', async () => {
await PageObjects.visualize.gotoVisualizationLandingPage();
await listingTable.searchForItemWithName('Artistpreviouslyknownaslens');
await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens');
await PageObjects.lens.goToTimeRange();
await PageObjects.lens.assertMetric('Maximum of bytes', '19,986');
await PageObjects.lens.save('Artistpreviouslyknownaslens Copy', true, false, 'new');
it('should allow existing lens be added by value to a new dashboard', async () => {
await loadExistingLens();
await PageObjects.lens.save('Artistpreviouslyknownaslens Copy', true, false, false, 'new');
await PageObjects.dashboard.waitForRenderComplete();
await PageObjects.lens.assertMetric('Maximum of bytes', '19,986');
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists(
'Artistpreviouslyknownaslens Copy'
);
expect(isLinked).to.be(false);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
@ -71,38 +99,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.timeToVisualize.resetNewDashboard();
});
it('should allow new lens vizs be added to an existing dashboard', async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await dashboardAddPanel.clickOpenAddPanel();
await dashboardAddPanel.filterEmbeddableNames('lnsXYvis');
await find.clickByButtonText('lnsXYvis');
await dashboardAddPanel.closeAddPanel();
await PageObjects.lens.goToTimeRange();
await PageObjects.dashboard.saveDashboard('My Very Cool Dashboard');
await PageObjects.dashboard.gotoDashboardLandingPage();
await listingTable.searchAndExpectItemsCount('dashboard', 'My Very Cool Dashboard', 1);
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('lens');
await PageObjects.lens.goToTimeRange();
await PageObjects.lens.configureDimension({
dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension',
operation: 'avg',
field: 'bytes',
});
await PageObjects.lens.switchToVisualization('lnsMetric');
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.lens.assertMetric('Average of bytes', '5,727.322');
it('should allow new lens be added by value to an existing dashboard', async () => {
await createAndSaveDashboard('My Very Cool Dashboard');
await createNewLens();
await PageObjects.lens.save(
'New Lens from Modal',
false,
false,
false,
'existing',
'My Very Cool Dashboard'
);
@ -110,34 +115,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.dashboard.waitForRenderComplete();
await PageObjects.lens.assertMetric('Average of bytes', '5,727.322');
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists(
'New Lens from Modal'
);
expect(isLinked).to.be(false);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(2);
});
it('should allow existing lens vizs be added to an existing dashboard', async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await dashboardAddPanel.clickOpenAddPanel();
await dashboardAddPanel.filterEmbeddableNames('lnsXYvis');
await find.clickByButtonText('lnsXYvis');
await dashboardAddPanel.closeAddPanel();
await PageObjects.lens.goToTimeRange();
await PageObjects.dashboard.saveDashboard('My Wonderful Dashboard');
await PageObjects.dashboard.gotoDashboardLandingPage();
await listingTable.searchAndExpectItemsCount('dashboard', 'My Wonderful Dashboard', 1);
await PageObjects.visualize.gotoVisualizationLandingPage();
await listingTable.searchForItemWithName('Artistpreviouslyknownaslens');
await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens');
await PageObjects.lens.goToTimeRange();
await PageObjects.lens.assertMetric('Maximum of bytes', '19,986');
it('should allow existing lens be added by value to an existing dashboard', async () => {
await createAndSaveDashboard('My Wonderful Dashboard');
await loadExistingLens();
await PageObjects.lens.save(
'Artistpreviouslyknownaslens Copy',
true,
false,
false,
'existing',
'My Wonderful Dashboard'
);
@ -145,6 +140,96 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.dashboard.waitForRenderComplete();
await PageObjects.lens.assertMetric('Maximum of bytes', '19,986');
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists(
'Artistpreviouslyknownaslens Copy'
);
expect(isLinked).to.be(false);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(2);
});
it('should allow new lens to be added by reference to a new dashboard', async () => {
await createNewLens();
await PageObjects.lens.save('New by ref Lens from Modal', false, false, true, 'new');
await PageObjects.dashboard.waitForRenderComplete();
await PageObjects.lens.assertMetric('Average of bytes', '5,727.322');
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists(
'New by ref Lens from Modal'
);
expect(isLinked).to.be(true);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
await PageObjects.timeToVisualize.resetNewDashboard();
});
it('should allow existing lens be added by reference to a new dashboard', async () => {
await loadExistingLens();
await PageObjects.lens.save('Artistpreviouslyknownaslens by ref', true, false, true, 'new');
await PageObjects.dashboard.waitForRenderComplete();
await PageObjects.lens.assertMetric('Maximum of bytes', '19,986');
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists(
'Artistpreviouslyknownaslens by ref'
);
expect(isLinked).to.be(true);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
await PageObjects.timeToVisualize.resetNewDashboard();
});
it('should allow new lens be added by reference to an existing dashboard', async () => {
await createAndSaveDashboard('My Very Cool Dashboard 2');
await createNewLens();
await PageObjects.lens.save(
'New Lens by ref from Modal',
false,
false,
true,
'existing',
'My Very Cool Dashboard 2'
);
await PageObjects.dashboard.waitForRenderComplete();
await PageObjects.lens.assertMetric('Average of bytes', '5,727.322');
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists(
'New Lens by ref from Modal'
);
expect(isLinked).to.be(true);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(2);
});
it('should allow existing lens be added by reference to an existing dashboard', async () => {
await createAndSaveDashboard('My Wonderful Dashboard 2');
await loadExistingLens();
await PageObjects.lens.save(
'Artistpreviouslyknownaslens by ref 2',
true,
false,
true,
'existing',
'My Wonderful Dashboard 2'
);
await PageObjects.dashboard.waitForRenderComplete();
await PageObjects.lens.assertMetric('Maximum of bytes', '19,986');
const isLinked = await PageObjects.timeToVisualize.libraryNotificationExists(
'Artistpreviouslyknownaslens by ref 2'
);
expect(isLinked).to.be(true);
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(2);

View file

@ -39,27 +39,33 @@ export default function ({ getPageObjects, getService }) {
await security.testUser.restoreDefaults();
});
it('should allow new map be added to a new dashboard', async () => {
it('should allow new map be added by value to a new dashboard', async () => {
await PageObjects.maps.openNewMap();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.maps.waitForLayersToLoad();
await testSubjects.click('mapSaveButton');
await PageObjects.timeToVisualize.saveFromModal('map 1', { addToDashboard: 'new' });
await PageObjects.timeToVisualize.saveFromModal('map 1', {
addToDashboard: 'new',
saveToLibrary: false,
});
await PageObjects.dashboard.waitForRenderComplete();
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
const isInLibrary = await PageObjects.timeToVisualize.libraryNotificationExists('map 1');
expect(isInLibrary).to.be(false);
await PageObjects.timeToVisualize.resetNewDashboard();
});
it('should allow existing maps be added to a new dashboard', async () => {
it('should allow existing maps be added by value to a new dashboard', async () => {
await PageObjects.maps.loadSavedMap('document example');
await testSubjects.click('mapSaveButton');
await PageObjects.timeToVisualize.saveFromModal('document example copy', {
saveToLibrary: false,
addToDashboard: 'new',
saveAsNew: true,
});
@ -69,10 +75,14 @@ export default function ({ getPageObjects, getService }) {
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
const isInLibrary = await PageObjects.timeToVisualize.libraryNotificationExists(
'document example copy'
);
expect(isInLibrary).to.be(false);
await PageObjects.timeToVisualize.resetNewDashboard();
});
it('should allow new map be added to an existing dashboard', async () => {
it('should allow new map be added by value to an existing dashboard', async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
@ -86,6 +96,7 @@ export default function ({ getPageObjects, getService }) {
await testSubjects.click('mapSaveButton');
await PageObjects.timeToVisualize.saveFromModal('My New Map 2', {
saveToLibrary: false,
addToDashboard: 'existing',
dashboardId: 'My Very Cool Dashboard',
});
@ -94,9 +105,14 @@ export default function ({ getPageObjects, getService }) {
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
const isInLibrary = await PageObjects.timeToVisualize.libraryNotificationExists(
'My New Map 2'
);
expect(isInLibrary).to.be(false);
});
it('should allow existing maps be added to an existing dashboard', async () => {
it('should allow existing maps be added by value to an existing dashboard', async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
@ -108,6 +124,7 @@ export default function ({ getPageObjects, getService }) {
await testSubjects.click('mapSaveButton');
await PageObjects.timeToVisualize.saveFromModal('document example copy 2', {
saveToLibrary: false,
addToDashboard: 'existing',
dashboardId: 'My Wonderful Dashboard',
saveAsNew: true,
@ -117,6 +134,113 @@ export default function ({ getPageObjects, getService }) {
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
const isInLibrary = await PageObjects.timeToVisualize.libraryNotificationExists(
'document example copy 2'
);
expect(isInLibrary).to.be(false);
});
it('should allow new map be added by reference to a new dashboard', async () => {
await PageObjects.maps.openNewMap();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.maps.waitForLayersToLoad();
await testSubjects.click('mapSaveButton');
await PageObjects.timeToVisualize.saveFromModal('map 1', {
addToDashboard: 'new',
saveToLibrary: true,
});
await PageObjects.dashboard.waitForRenderComplete();
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
const isInLibrary = await PageObjects.timeToVisualize.libraryNotificationExists('map 1');
expect(isInLibrary).to.be(true);
await PageObjects.timeToVisualize.resetNewDashboard();
});
it('should allow existing maps be added by reference to a new dashboard', async () => {
await PageObjects.maps.loadSavedMap('document example');
await testSubjects.click('mapSaveButton');
await PageObjects.timeToVisualize.saveFromModal('document example copy', {
saveToLibrary: true,
addToDashboard: 'new',
saveAsNew: true,
});
await PageObjects.dashboard.waitForRenderComplete();
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
const isInLibrary = await PageObjects.timeToVisualize.libraryNotificationExists(
'document example copy'
);
expect(isInLibrary).to.be(true);
await PageObjects.timeToVisualize.resetNewDashboard();
});
it('should allow new map be added by reference to an existing dashboard', async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.saveDashboard('My Super Cool Dashboard');
await PageObjects.dashboard.gotoDashboardLandingPage();
await listingTable.searchAndExpectItemsCount('dashboard', 'My Super Cool Dashboard', 1);
await PageObjects.maps.openNewMap();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.maps.waitForLayersToLoad();
await testSubjects.click('mapSaveButton');
await PageObjects.timeToVisualize.saveFromModal('My New Map 2', {
saveToLibrary: true,
addToDashboard: 'existing',
dashboardId: 'My Super Cool Dashboard',
});
await PageObjects.dashboard.waitForRenderComplete();
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
const isInLibrary = await PageObjects.timeToVisualize.libraryNotificationExists(
'My New Map 2'
);
expect(isInLibrary).to.be(true);
});
it('should allow existing maps be added by reference to an existing dashboard', async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.saveDashboard('My Amazing Dashboard');
await PageObjects.dashboard.gotoDashboardLandingPage();
await listingTable.searchAndExpectItemsCount('dashboard', 'My Amazing Dashboard', 1);
await PageObjects.maps.loadSavedMap('document example');
await testSubjects.click('mapSaveButton');
await PageObjects.timeToVisualize.saveFromModal('document example copy 2', {
saveToLibrary: true,
addToDashboard: 'existing',
dashboardId: 'My Amazing Dashboard',
saveAsNew: true,
});
await PageObjects.dashboard.waitForRenderComplete();
const panelCount = await PageObjects.dashboard.getPanelCount();
expect(panelCount).to.eql(1);
const isInLibrary = await PageObjects.timeToVisualize.libraryNotificationExists(
'document example copy 2'
);
expect(isInLibrary).to.be(true);
});
});
}

View file

@ -349,6 +349,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
title: string,
saveAsNew?: boolean,
redirectToOrigin?: boolean,
saveToLibrary?: boolean,
addToDashboard?: 'new' | 'existing' | null,
dashboardId?: string
) {
@ -360,6 +361,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
redirectToOrigin,
addToDashboard: addToDashboard ? addToDashboard : null,
dashboardId,
saveToLibrary,
});
await testSubjects.click('confirmSaveSavedObjectButton');