Use enter to submit save modals (#34417)

Now you can use the Enter key to submit the form used e.g. when saving a new dashboard
Good for a11y + a time saver

Adds Form element, migration of Button onClick to Form's onSubmit 
Adds functional test for Enter key submission

Fix #30831
This commit is contained in:
Matthias Wilhelm 2019-04-05 10:47:30 +02:00 committed by GitHub
parent 05b842c471
commit eceeb9fb35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 185 additions and 138 deletions

View file

@ -8,81 +8,84 @@ exports[`SavedObjectSaveModal should render matching snapshot 1`] = `
maxWidth={true}
onClose={[Function]}
>
<EuiModalHeader>
<EuiModalHeaderTitle>
<FormattedMessage
defaultMessage="Save {objectType}"
id="common.ui.savedObjects.saveModal.saveTitle"
values={
Object {
"objectType": "visualization",
<form
onSubmit={[Function]}
>
<EuiModalHeader>
<EuiModalHeaderTitle>
<FormattedMessage
defaultMessage="Save {objectType}"
id="common.ui.savedObjects.saveModal.saveTitle"
values={
Object {
"objectType": "visualization",
}
}
}
/>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiForm>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label={
<FormattedMessage
defaultMessage="Title"
id="common.ui.savedObjects.saveModal.titleLabel"
values={Object {}}
/>
}
labelType="label"
>
<EuiFieldText
autoFocus={true}
compressed={false}
data-test-subj="savedObjectTitle"
fullWidth={false}
isInvalid={false}
isLoading={false}
onChange={[Function]}
value="Saved Object title"
/>
</EuiFormRow>
</EuiForm>
</EuiModalBody>
<EuiModalFooter>
<EuiButton
color="primary"
data-test-subj="saveCancelButton"
fill={false}
iconSide="left"
onClick={[Function]}
size="m"
type="button"
>
<FormattedMessage
defaultMessage="Cancel"
id="common.ui.savedObjects.saveModal.cancelButtonLabel"
values={Object {}}
/>
</EuiButton>
<EuiButton
color="primary"
data-test-subj="confirmSaveSavedObjectButton"
fill={true}
iconSide="left"
isDisabled={false}
isLoading={false}
onClick={[Function]}
size="m"
type="button"
>
<FormattedMessage
defaultMessage="Confirm Save"
id="common.ui.savedObjects.saveModal.confirmSaveButtonLabel"
values={Object {}}
/>
</EuiButton>
</EuiModalFooter>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiForm>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label={
<FormattedMessage
defaultMessage="Title"
id="common.ui.savedObjects.saveModal.titleLabel"
values={Object {}}
/>
}
labelType="label"
>
<EuiFieldText
autoFocus={true}
compressed={false}
data-test-subj="savedObjectTitle"
fullWidth={false}
isInvalid={false}
isLoading={false}
onChange={[Function]}
value="Saved Object title"
/>
</EuiFormRow>
</EuiForm>
</EuiModalBody>
<EuiModalFooter>
<EuiButton
color="primary"
data-test-subj="saveCancelButton"
fill={false}
iconSide="left"
onClick={[Function]}
size="m"
type="button"
>
<FormattedMessage
defaultMessage="Cancel"
id="common.ui.savedObjects.saveModal.cancelButtonLabel"
values={Object {}}
/>
</EuiButton>
<EuiButton
color="primary"
data-test-subj="confirmSaveSavedObjectButton"
fill={true}
iconSide="left"
isDisabled={false}
isLoading={false}
size="m"
type="submit"
>
<FormattedMessage
defaultMessage="Confirm Save"
id="common.ui.savedObjects.saveModal.confirmSaveButtonLabel"
values={Object {}}
/>
</EuiButton>
</EuiModalFooter>
</form>
</EuiModal>
</EuiOverlayMask>
`;

View file

@ -63,7 +63,7 @@ export class SavedObjectSaveModal extends React.Component {
isTitleDuplicateConfirmed: true,
hasTitleDuplicate: true,
});
}
};
saveSavedObject = async () => {
if (this.state.isLoading) {
@ -95,7 +95,12 @@ export class SavedObjectSaveModal extends React.Component {
this.setState({
copyOnSave: event.target.checked,
});
}
};
onFormSubmit = (event) => {
event.preventDefault();
this.saveSavedObject();
};
renderDuplicateTitleCallout = () => {
if (!this.state.hasTitleDuplicate) {
@ -134,7 +139,7 @@ export class SavedObjectSaveModal extends React.Component {
<EuiSpacer />
</Fragment>
);
}
};
renderCopyOnSave = () => {
if (!this.props.showCopyOnSave) {
@ -156,9 +161,11 @@ export class SavedObjectSaveModal extends React.Component {
/>
</EuiFormRow>
);
}
};
render() {
const { isTitleDuplicateConfirmed, hasTitleDuplicate, title, isLoading } = this.state;
return (
<EuiOverlayMask>
<EuiModal
@ -166,74 +173,76 @@ export class SavedObjectSaveModal extends React.Component {
className="dshSaveModal"
onClose={this.props.onClose}
>
<EuiModalHeader>
<EuiModalHeaderTitle>
<FormattedMessage
id="common.ui.savedObjects.saveModal.saveTitle"
defaultMessage="Save {objectType}"
values={{ objectType: this.props.objectType }}
/>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
{this.renderDuplicateTitleCallout()}
<EuiForm>
{this.renderCopyOnSave()}
<EuiFormRow
label={(<FormattedMessage
id="common.ui.savedObjects.saveModal.titleLabel"
defaultMessage="Title"
/>)}
>
<EuiFieldText
autoFocus
data-test-subj="savedObjectTitle"
value={this.state.title}
onChange={this.onTitleChange}
isInvalid={this.state.hasTitleDuplicate || this.state.title.length === 0}
<form onSubmit={this.onFormSubmit}>
<EuiModalHeader>
<EuiModalHeaderTitle>
<FormattedMessage
id="common.ui.savedObjects.saveModal.saveTitle"
defaultMessage="Save {objectType}"
values={{ objectType: this.props.objectType }}
/>
</EuiFormRow>
</EuiModalHeaderTitle>
</EuiModalHeader>
{this.props.options}
<EuiModalBody>
</EuiForm>
{this.renderDuplicateTitleCallout()}
</EuiModalBody>
<EuiForm>
<EuiModalFooter>
<EuiButton
data-test-subj="saveCancelButton"
onClick={this.props.onClose}
>
<FormattedMessage
id="common.ui.savedObjects.saveModal.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButton>
{this.renderCopyOnSave()}
<EuiButton
fill
data-test-subj="confirmSaveSavedObjectButton"
onClick={this.saveSavedObject}
isLoading={this.state.isLoading}
isDisabled={this.state.title.length === 0}
>
{this.props.confirmButtonLabel
? this.props.confirmButtonLabel
: (
<FormattedMessage
id="common.ui.savedObjects.saveModal.confirmSaveButtonLabel"
defaultMessage="Confirm Save"
<EuiFormRow
label={(<FormattedMessage
id="common.ui.savedObjects.saveModal.titleLabel"
defaultMessage="Title"
/>)}
>
<EuiFieldText
autoFocus
data-test-subj="savedObjectTitle"
value={title}
onChange={this.onTitleChange}
isInvalid={(!isTitleDuplicateConfirmed && hasTitleDuplicate) || title.length === 0}
/>
)
}
</EuiButton>
</EuiModalFooter>
</EuiFormRow>
{this.props.options}
</EuiForm>
</EuiModalBody>
<EuiModalFooter>
<EuiButton
data-test-subj="saveCancelButton"
onClick={this.props.onClose}
>
<FormattedMessage
id="common.ui.savedObjects.saveModal.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButton>
<EuiButton
fill
data-test-subj="confirmSaveSavedObjectButton"
isLoading={isLoading}
isDisabled={title.length === 0}
type="submit"
>
{this.props.confirmButtonLabel
? this.props.confirmButtonLabel
: (
<FormattedMessage
id="common.ui.savedObjects.saveModal.confirmSaveButtonLabel"
defaultMessage="Confirm Save"
/>
)
}
</EuiButton>
</EuiModalFooter>
</form>
</EuiModal>
</EuiOverlayMask>
);

View file

@ -24,6 +24,7 @@ export default function ({ getPageObjects }) {
describe('dashboard save', function describeIndexTests() {
const dashboardName = 'Dashboard Save Test';
const dashboardNameEnterKey = 'Dashboard Save Test with Enter Key';
before(async function () {
await PageObjects.dashboard.initTests();
@ -102,5 +103,20 @@ export default function ({ getPageObjects }) {
await PageObjects.dashboard.cancelSave();
});
it('Saves new Dashboard using the Enter key', async function () {
await PageObjects.dashboard.gotoDashboardLandingPage();
await PageObjects.dashboard.clickNewDashboard();
await PageObjects.dashboard.enterDashboardTitleAndPressEnter(dashboardNameEnterKey);
// 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
// dashboard landing page click.
await PageObjects.header.waitUntilLoadingHasFinished();
const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName(dashboardNameEnterKey);
expect(countOfDashboards).to.equal(1);
});
});
}

View file

@ -349,6 +349,11 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
await testSubjects.clickWhenNotDisabled('confirmSaveSavedObjectButton');
}
async pressEnterKey() {
log.debug('DashboardPage.pressEnterKey');
await PageObjects.common.pressEnterKey();
}
/**
*
* @param dashboardTitle {String}
@ -375,6 +380,20 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
}
}
/**
* @param dashboardTitle {String}
*/
async enterDashboardTitleAndPressEnter(dashboardTitle) {
await testSubjects.click('dashboardSaveMenuItem');
const modalDialog = await testSubjects.find('savedObjectSaveModal');
log.debug('entering new title');
await testSubjects.setValue('savedObjectTitle', dashboardTitle);
await this.pressEnterKey();
await testSubjects.waitForDeleted(modalDialog);
}
async selectDashboard(dashName) {
await testSubjects.click(`dashboardListingTitleLink-${dashName.split(' ').join('-')}`);
}