Convert Dashboard save dialog to react/EUI (#19531)

* eui save panel

* add title and description inputs

* copy on save

* add jest tests

* fix functional and jest tests

* another functional test fix

* updates from Stacey-Gammon review

* remove debounce because it broke functional tests

* update jest snapshot
This commit is contained in:
Nathan Reese 2018-05-31 08:28:47 -06:00 committed by GitHub
parent 8d1629a7a6
commit 3dae391e89
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 513 additions and 130 deletions

View file

@ -40,6 +40,7 @@ import { VisualizeConstants } from '../visualize/visualize_constants';
import { DashboardStateManager } from './dashboard_state_manager';
import { saveDashboard } from './lib';
import { showCloneModal } from './top_nav/show_clone_modal';
import { showSaveModal } from './top_nav/show_save_modal';
import { showAddPanel } from './top_nav/show_add_panel';
import { migrateLegacyQuery } from 'ui/utils/migrateLegacyQuery';
import * as filterActions from 'ui/doc_table/actions/filter';
@ -219,9 +220,6 @@ app.directive('dashboardApp', function ($injector) {
dashboardStateManager.setDarkTheme($scope.model.darkTheme);
updateTheme();
});
$scope.$watch('model.description', () => dashboardStateManager.setDescription($scope.model.description));
$scope.$watch('model.title', () => dashboardStateManager.setTitle($scope.model.title));
$scope.$watch('model.timeRestore', () => dashboardStateManager.setTimeRestore($scope.model.timeRestore));
$scope.indexPatterns = [];
$scope.onPanelRemoved = (panelIndex) => {
@ -329,8 +327,41 @@ app.directive('dashboardApp', function ($injector) {
dashboardStateManager.setFullScreenMode(true);
navActions[TopNavIds.EXIT_EDIT_MODE] = () => onChangeViewMode(DashboardViewMode.VIEW);
navActions[TopNavIds.ENTER_EDIT_MODE] = () => onChangeViewMode(DashboardViewMode.EDIT);
navActions[TopNavIds.SAVE] = () => {
const currentTitle = dashboardStateManager.getTitle();
const currentDescription = dashboardStateManager.getDescription();
const currentTimeRestore = dashboardStateManager.getTimeRestore();
const onSave = ({ newTitle, newDescription, newCopyOnSave, newTimeRestore, isTitleDuplicateConfirmed, onTitleDuplicate }) => {
dashboardStateManager.setTitle(newTitle);
dashboardStateManager.setDescription(newDescription);
dashboardStateManager.savedDashboard.copyOnSave = newCopyOnSave;
dashboardStateManager.setTimeRestore(newTimeRestore);
const saveOptions = {
confirmOverwrite: false,
isTitleDuplicateConfirmed,
onTitleDuplicate,
};
return $scope.save(saveOptions).then(id => {
// If the save wasn't successful, put the original values back.
if (!id) {
dashboardStateManager.setTitle(currentTitle);
dashboardStateManager.setDescription(currentDescription);
dashboardStateManager.setTimeRestore(currentTimeRestore);
}
return id;
});
};
showSaveModal({
onSave,
title: currentTitle,
description: currentDescription,
timeRestore: currentTimeRestore,
showCopyOnSave: dash.id ? true : false,
});
};
navActions[TopNavIds.CLONE] = () => {
const currentTitle = $scope.model.title;
const currentTitle = dashboardStateManager.getTitle();
const onClone = (newTitle, isTitleDuplicateConfirmed, onTitleDuplicate) => {
dashboardStateManager.savedDashboard.copyOnSave = true;
dashboardStateManager.setTitle(newTitle);
@ -342,9 +373,6 @@ app.directive('dashboardApp', function ($injector) {
return $scope.save(saveOptions).then(id => {
// If the save wasn't successful, put the original title back.
if (!id) {
$scope.model.title = currentTitle;
// There is a watch on $scope.model.title that *should* call this automatically but
// angular is failing to trigger it, so do so manually here.
dashboardStateManager.setTitle(currentTitle);
}
return id;
@ -406,11 +434,9 @@ app.directive('dashboardApp', function ($injector) {
kbnUrl.removeParam(DashboardConstants.NEW_VISUALIZATION_ID_PARAM);
}
// TODO remove opts once share has been converted to react
$scope.opts = {
displayName: dash.getDisplayName(),
dashboard: dash,
save: $scope.save,
timefilter: $scope.timefilter
dashboard: dash, // used in share.html
};
}
};

View file

@ -109,7 +109,7 @@
}
.dashboardCloneModal {
.dashboardModal {
width: 450px;
}

View file

@ -3,7 +3,7 @@
exports[`renders DashboardCloneModal 1`] = `
<EuiOverlayMask>
<EuiModal
className="dashboardCloneModal"
className="dashboardModal"
data-test-subj="dashboardCloneModal"
onClose={[Function]}
>

View file

@ -0,0 +1,100 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders DashboardSaveModal 1`] = `
<EuiOverlayMask>
<EuiModal
className="dashboardModal"
data-test-subj="dashboardSaveModal"
onClose={[Function]}
>
<EuiModalHeader>
<EuiModalHeaderTitle>
Save Dashboard
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiForm>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Save as a new dashboard"
>
<EuiSwitch
checked={false}
data-test-subj="saveAsNewCheckbox"
onChange={[Function]}
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Title"
>
<EuiFieldText
autoFocus={true}
compressed={false}
data-test-subj="dashboardTitle"
fullWidth={false}
isInvalid={false}
isLoading={false}
onChange={[Function]}
value="dash title"
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Description"
>
<EuiTextArea
compressed={true}
data-test-subj="dashboardDescription"
fullWidth={false}
onChange={[Function]}
value="dash description"
/>
</EuiFormRow>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
helpText="This changes the time filter to the currently selected time each time this dashboard is loaded."
label="Store time with dashboard"
>
<EuiSwitch
checked={true}
data-test-subj="storeTimeWithDashboard"
onChange={[Function]}
/>
</EuiFormRow>
</EuiForm>
</EuiModalBody>
<EuiModalFooter>
<EuiButton
color="primary"
data-test-subj="saveCancelButton"
fill={false}
iconSide="left"
onClick={[Function]}
type="button"
>
Cancel
</EuiButton>
<EuiButton
color="primary"
data-test-subj="confirmSaveDashboardButton"
fill={true}
iconSide="left"
isLoading={false}
onClick={[Function]}
type="button"
>
Confirm Save
</EuiButton>
</EuiModalFooter>
</EuiModal>
</EuiOverlayMask>
`;

View file

@ -92,7 +92,7 @@ export class DashboardCloneModal extends React.Component {
<EuiCallOut
title={`A Dashboard with the title '${this.state.newDashboardName}' already exists.`}
color="warning"
data-test-subj="cloneModalTitleDupicateWarnMsg"
data-test-subj="titleDupicateWarnMsg"
>
<p>
Click <strong>Confirm Clone</strong> to clone the dashboard with the duplicate title.
@ -108,7 +108,7 @@ export class DashboardCloneModal extends React.Component {
<EuiOverlayMask>
<EuiModal
data-test-subj="dashboardCloneModal"
className="dashboardCloneModal"
className="dashboardModal"
onClose={this.props.onClose}
>
<EuiModalHeader>

View file

@ -45,7 +45,7 @@ export function getTopNavConfig(dashboardMode, actions, hideWriteControls) {
);
case DashboardViewMode.EDIT:
return [
getSaveConfig(),
getSaveConfig(actions[TopNavIds.SAVE]),
getViewConfig(actions[TopNavIds.EXIT_EDIT_MODE]),
getAddConfig(actions[TopNavIds.ADD]),
getOptionsConfig(),
@ -79,12 +79,12 @@ function getEditConfig(action) {
/**
* @returns {kbnTopNavConfig}
*/
function getSaveConfig() {
function getSaveConfig(action) {
return {
key: 'save',
key: TopNavIds.SAVE,
description: 'Save your dashboard',
testId: 'dashboardSaveMenuItem',
template: require('plugins/kibana/dashboard/top_nav/save.html')
run: action
};
}
@ -105,7 +105,7 @@ function getViewConfig(action) {
*/
function getCloneConfig(action) {
return {
key: 'clone',
key: TopNavIds.CLONE,
description: 'Create a copy of your dashboard',
testId: 'dashboardClone',
run: action

View file

@ -1,84 +0,0 @@
<form
role="form"
ng-submit="opts.save()"
>
<h2 class="kuiLocalDropdownTitle">
Save {{opts.displayName}}
</h2>
<div class="kuiLocalDropdownSection">
<div class="kuiLocalDropdownHeader">
<label
class="kuiLocalDropdownHeader__label"
for="saveDashboardTitle"
>
Title
</label>
</div>
<input
id="saveDashboardTitle"
class="kuiLocalDropdownInput"
data-test-subj="dashboardTitle"
type="text"
ng-model="model.title"
placeholder="Dashboard title"
input-focus="select"
>
</div>
<div class="kuiLocalDropdownSection">
<div class="kuiLocalDropdownHeader">
<label
class="kuiLocalDropdownHeader__label"
for="saveDashboardDescription"
>
Description
</label>
</div>
<input
id="saveDashboardDescription"
class="kuiLocalDropdownInput"
data-test-subj="dashboardDescription"
type="text"
ng-model="model.description"
placeholder="Dashboard description"
>
</div>
<saved-object-save-as-check-box
class="kuiVerticalRhythmSmall"
saved-object="opts.dashboard"
></saved-object-save-as-check-box>
<div class="kuiVerticalRhythm">
<label class="kuiCheckBoxLabel kuiVerticalRhythmSmall">
<input
class="kuiCheckBox"
type="checkbox"
ng-model="model.timeRestore"
ng-checked="model.timeRestore"
data-test-subj="storeTimeWithDashboard"
>
<span class="kuiCheckBoxLabel__text">
Store time with {{opts.displayName}}
</span>
</label>
<div class="kuiLocalDropdownFormNote kuiVerticalRhythmSmall">
This changes the time filter to the currently selected time each time this dashboard is loaded.
</div>
</div>
<button
data-test-subj="confirmSaveDashboardButton"
type="submit"
ng-disabled="!model.title"
class="kuiButton kuiButton--primary kuiVerticalRhythm"
aria-label="Save dashboard"
>
Save
</button>
</form>

View file

@ -0,0 +1,245 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import {
EuiButton,
EuiFieldText,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiOverlayMask,
EuiSpacer,
EuiCallOut,
EuiForm,
EuiFormRow,
EuiTextArea,
EuiSwitch,
} from '@elastic/eui';
export class DashboardSaveModal extends React.Component {
constructor(props) {
super(props);
this.state = {
title: props.title,
description: props.description,
copyOnSave: false,
timeRestore: props.timeRestore,
isTitleDuplicateConfirmed: false,
hasTitleDuplicate: false,
isLoading: false,
};
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
onTitleDuplicate = () => {
this.setState({
isLoading: false,
isTitleDuplicateConfirmed: true,
hasTitleDuplicate: true,
});
}
saveDashboard = async () => {
if (this.state.isLoading) {
// ignore extra clicks
return;
}
this.setState({
isLoading: true,
});
await this.props.onSave({
newTitle: this.state.title,
newDescription: this.state.description,
newCopyOnSave: this.state.copyOnSave,
newTimeRestore: this.state.timeRestore,
isTitleDuplicateConfirmed: this.state.isTitleDuplicateConfirmed,
onTitleDuplicate: this.onTitleDuplicate,
});
};
onTitleChange = (event) => {
this.setState({
title: event.target.value,
isTitleDuplicateConfirmed: false,
hasTitleDuplicate: false,
});
};
onDescriptionChange = (event) => {
this.setState({
description: event.target.value,
});
};
onCopyOnSaveChange = (event) => {
this.setState({
copyOnSave: event.target.checked,
});
}
onTimeRestoreChange = (event) => {
this.setState({
timeRestore: event.target.checked,
});
}
renderDuplicateTitleCallout = () => {
if (!this.state.hasTitleDuplicate) {
return;
}
return (
<Fragment>
<EuiCallOut
title={`A Dashboard with the title '${this.state.title}' already exists.`}
color="warning"
data-test-subj="titleDupicateWarnMsg"
>
<p>
Click <strong>Confirm Save</strong> to save the dashboard with the duplicate title.
</p>
</EuiCallOut>
<EuiSpacer />
</Fragment>
);
}
renderCopyOnSave = () => {
if (!this.props.showCopyOnSave) {
return;
}
return (
<EuiFormRow
label="Save as a new dashboard"
>
<EuiSwitch
data-test-subj="saveAsNewCheckbox"
checked={this.state.copyOnSave}
onChange={this.onCopyOnSaveChange}
/>
</EuiFormRow>
);
}
render() {
return (
<EuiOverlayMask>
<EuiModal
data-test-subj="dashboardSaveModal"
className="dashboardModal"
onClose={this.props.onClose}
>
<EuiModalHeader>
<EuiModalHeaderTitle>
Save Dashboard
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
{this.renderDuplicateTitleCallout()}
<EuiForm>
{this.renderCopyOnSave()}
<EuiFormRow
label="Title"
>
<EuiFieldText
autoFocus
data-test-subj="dashboardTitle"
value={this.state.title}
onChange={this.onTitleChange}
isInvalid={this.state.hasTitleDuplicate}
/>
</EuiFormRow>
<EuiFormRow
label="Description"
>
<EuiTextArea
data-test-subj="dashboardDescription"
value={this.state.description}
onChange={this.onDescriptionChange}
compressed
/>
</EuiFormRow>
<EuiFormRow
label="Store time with dashboard"
helpText="This changes the time filter to the currently selected time each time this dashboard is loaded."
>
<EuiSwitch
data-test-subj="storeTimeWithDashboard"
checked={this.state.timeRestore}
onChange={this.onTimeRestoreChange}
/>
</EuiFormRow>
</EuiForm>
</EuiModalBody>
<EuiModalFooter>
<EuiButton
data-test-subj="saveCancelButton"
onClick={this.props.onClose}
>
Cancel
</EuiButton>
<EuiButton
fill
data-test-subj="confirmSaveDashboardButton"
onClick={this.saveDashboard}
isLoading={this.state.isLoading}
>
Confirm Save
</EuiButton>
</EuiModalFooter>
</EuiModal>
</EuiOverlayMask>
);
}
}
DashboardSaveModal.propTypes = {
onSave: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
timeRestore: PropTypes.bool.isRequired,
showCopyOnSave: PropTypes.bool.isRequired,
};

View file

@ -0,0 +1,37 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import {
DashboardSaveModal,
} from './save_modal';
test('renders DashboardSaveModal', () => {
const component = shallow(<DashboardSaveModal
onSave={() => {}}
onClose={() => {}}
title="dash title"
description="dash description"
timeRestore={true}
showCopyOnSave={true}
/>);
expect(component).toMatchSnapshot(); // eslint-disable-line
});

View file

@ -0,0 +1,50 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { DashboardSaveModal } from './save_modal';
import React from 'react';
import ReactDOM from 'react-dom';
export function showSaveModal({ onSave, title, description, timeRestore, showCopyOnSave }) {
const container = document.createElement('div');
const closeModal = () => {
ReactDOM.unmountComponentAtNode(container);
document.body.removeChild(container);
};
const onSaveConfirmed = (...args) => {
onSave(...args).then(id => {
if (id) {
closeModal();
}
});
};
document.body.appendChild(container);
const element = (
<DashboardSaveModal
onSave={onSaveConfirmed}
onClose={closeModal}
title={title}
description={description}
timeRestore={timeRestore}
showCopyOnSave={showCopyOnSave}
/>
);
ReactDOM.render(element, container);
}

View file

@ -61,7 +61,7 @@ export default function ({ getService, getPageObjects }) {
it('and warns on duplicate name', async function () {
await PageObjects.dashboard.confirmClone();
const isWarningDisplayed = await PageObjects.dashboard.isCloneDuplicateTitleWarningDisplayed();
const isWarningDisplayed = await PageObjects.dashboard.isDuplicateTitleWarningDisplayed();
expect(isWarningDisplayed).to.equal(true);
});

View file

@ -21,7 +21,7 @@ import expect from 'expect.js';
export default function ({ getService, getPageObjects }) {
const retry = getService('retry');
const PageObjects = getPageObjects(['dashboard', 'header', 'common']);
const PageObjects = getPageObjects(['dashboard', 'header']);
describe('dashboard save', function describeIndexTests() {
const dashboardName = 'Dashboard Save Test';
@ -38,19 +38,19 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.saveDashboard(dashboardName);
let isConfirmOpen = await PageObjects.common.isConfirmModalOpen();
expect(isConfirmOpen).to.equal(false);
let isWarningDisplayed = await PageObjects.dashboard.isDuplicateTitleWarningDisplayed();
expect(isWarningDisplayed).to.equal(false);
await PageObjects.dashboard.gotoDashboardLandingPage();
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName);
isConfirmOpen = await PageObjects.common.isConfirmModalOpen();
expect(isConfirmOpen).to.equal(true);
isWarningDisplayed = await PageObjects.dashboard.isDuplicateTitleWarningDisplayed();
expect(isWarningDisplayed).to.equal(true);
});
it('does not save on reject confirmation', async function () {
await PageObjects.common.clickCancelOnModal();
await PageObjects.dashboard.cancelSave();
const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName(dashboardName);
expect(countOfDashboards).to.equal(1);
@ -61,7 +61,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName);
await PageObjects.common.clickConfirmOnModal();
await PageObjects.dashboard.clickSave();
// This is important since saving a new dashboard will cause a refresh of the page. We have to
// wait till it finishes reloading or it might reload the url after simulating the
@ -79,8 +79,8 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.saveDashboard(dashboardName);
const isConfirmOpen = await PageObjects.common.isConfirmModalOpen();
expect(isConfirmOpen).to.equal(false);
const isWarningDisplayed = await PageObjects.dashboard.isDuplicateTitleWarningDisplayed();
expect(isWarningDisplayed).to.equal(false);
}
);
@ -88,30 +88,30 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName, { saveAsNew: true });
const isConfirmOpen = await PageObjects.common.isConfirmModalOpen();
expect(isConfirmOpen).to.equal(true);
const isWarningDisplayed = await PageObjects.dashboard.isDuplicateTitleWarningDisplayed();
expect(isWarningDisplayed).to.equal(true);
await PageObjects.common.clickCancelOnModal();
await PageObjects.dashboard.cancelSave();
});
it('Does not warn when only the prefix matches', async function () {
await PageObjects.dashboard.saveDashboard(dashboardName.split(' ')[0]);
const isConfirmOpen = await PageObjects.common.isConfirmModalOpen();
expect(isConfirmOpen).to.equal(false);
const isWarningDisplayed = await PageObjects.dashboard.isDuplicateTitleWarningDisplayed();
expect(isWarningDisplayed).to.equal(false);
});
it('Warns when case is different', async function () {
await PageObjects.dashboard.clickEdit();
await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName.toUpperCase());
// We expect isConfirmModalOpen to be open, hence the retry if not found.
// We expect isWarningDisplayed to be open, hence the retry if not found.
await retry.try(async () => {
const isConfirmOpen = await PageObjects.common.isConfirmModalOpen();
expect(isConfirmOpen).to.equal(true);
const isWarningDisplayed = await PageObjects.dashboard.isDuplicateTitleWarningDisplayed();
expect(isWarningDisplayed).to.equal(true);
});
await PageObjects.common.clickCancelOnModal();
await PageObjects.dashboard.cancelSave();
});
});
}

View file

@ -167,8 +167,8 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
await testSubjects.setValue('clonedDashboardTitle', title);
}
async isCloneDuplicateTitleWarningDisplayed() {
return await testSubjects.exists('cloneModalTitleDupicateWarnMsg');
async isDuplicateTitleWarningDisplayed() {
return await testSubjects.exists('titleDupicateWarnMsg');
}
async clickEdit() {
@ -286,7 +286,7 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
await this.enterDashboardTitleAndClickSave(dashName, saveOptions);
if (saveOptions.needsConfirm) {
await PageObjects.common.clickConfirmOnModal();
await this.clickSave();
}
await PageObjects.header.waitUntilLoadingHasFinished();
@ -295,6 +295,18 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
return await testSubjects.exists('saveDashboardSuccess');
}
async cancelSave() {
log.debug('Canceling save');
await testSubjects.click('saveCancelButton');
}
async clickSave() {
await retry.try(async () => {
log.debug('clicking final Save button for named dashboard');
return await testSubjects.click('confirmSaveDashboardButton');
});
}
/**
*
* @param dashboardTitle {String}
@ -316,10 +328,7 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
await this.setSaveAsNewCheckBox(saveOptions.saveAsNew);
}
await retry.try(async () => {
log.debug('clicking final Save button for named dashboard');
return await testSubjects.click('confirmSaveDashboardButton');
});
await this.clickSave();
}
async selectDashboard(dashName) {