Disable footer keys when saving

To prevent double clicks on saving, disable the submit/save
keys on the DeveloperKey modal for both api and lti keys.

closes PLAT-3913

Test Plan:
 - Create an lti key and attempt to save right away, note
   that the button becomes disabled until it returns saying
   that the json was invalid
 - Create a valid tool config and press save, note that the
   save button disables until the next screen appears and
   reenables at that time
 - Save the customizations and note the same thing until the
   modal closes
 - do the same with an api key and note that saving disables
   the button until closes
 - also open the modal for each type and refresh teh page, note
   that it takes you back to the page with the modal open

Change-Id: Id1d1a2a2baa6fe00b2dafb406eec25040540b84c
Reviewed-on: https://gerrit.instructure.com/171481
Tested-by: Jenkins
Reviewed-by: Weston Dransfield <wdransfield@instructure.com>
QA-Review: Weston Dransfield <wdransfield@instructure.com>
Product-Review: Marc Phillips <mphillips@instructure.com>
This commit is contained in:
Marc Phillips 2018-11-07 14:58:08 -07:00
parent d3cd41bda0
commit 07132b9f92
8 changed files with 39 additions and 19 deletions

View File

@ -44,7 +44,7 @@ export default class LtiKeyFooter extends React.Component {
const buttonText = customizing ? I18n.t('Save Customizations') : I18n.t('Save and Customize')
return (
<Button onClick={clickHandler} variant="primary">
<Button onClick={clickHandler} variant="primary" disabled={this.props.disable}>
{buttonText}
</Button>
)
@ -67,5 +67,6 @@ LtiKeyFooter.propTypes = {
onCancelClick: PropTypes.func.isRequired,
onSaveClick: PropTypes.func.isRequired,
onAdvanceToCustomization: PropTypes.func.isRequired,
customizing: PropTypes.bool.isRequired
customizing: PropTypes.bool.isRequired,
disable: PropTypes.bool
}

View File

@ -27,7 +27,7 @@ const NewKeyFooter = props => {
return (
<ModalFooter>
<Button onClick={props.onCancelClick}>{I18n.t('Cancel')}</Button>&nbsp;
<Button onClick={props.onSaveClick} variant="primary">
<Button onClick={props.onSaveClick} variant="primary" disabled={props.disable}>
{I18n.t('Save Key')}
</Button>
</ModalFooter>
@ -36,7 +36,8 @@ const NewKeyFooter = props => {
NewKeyFooter.propTypes = {
onCancelClick: PropTypes.func.isRequired,
onSaveClick: PropTypes.func.isRequired
onSaveClick: PropTypes.func.isRequired,
disable: PropTypes.bool
}
export default NewKeyFooter

View File

@ -129,12 +129,12 @@ export default class DeveloperKeyModal extends React.Component {
return this.props.createLtiKeyState.isLtiKey
}
get isSaving() {
return this.props.createOrEditDeveloperKeyState.developerKeyCreateOrEditPending || this.props.createLtiKeyState.saveToolConfigurationPending
}
modalBody() {
const isSavingDeveloperKey = this.props.createOrEditDeveloperKeyState.developerKeyCreateOrEditPending
const isSavingLtiToolConfig = this.props.createLtiKeyState.saveToolConfigurationPending
if (
isSavingDeveloperKey || isSavingLtiToolConfig
) {
if (this.isSaving) {
return this.spinner()
}
return this.developerKeyForm()
@ -142,19 +142,22 @@ export default class DeveloperKeyModal extends React.Component {
modalFooter() {
if (this.isLtiKey) {
const { createLtiKeyState, store, actions } = this.props
return(
<LtiKeyFooter
onCancelClick={this.closeModal}
onSaveClick={this.saveCustomizations}
onAdvanceToCustomization={this.saveLtiToolConfiguration}
customizing={this.props.createLtiKeyState.customizing}
ltiKeysSetCustomizing={this.props.actions.ltiKeysSetCustomizing}
dispatch={this.props.store.dispatch}
customizing={createLtiKeyState.customizing}
disable={this.isSaving}
ltiKeysSetCustomizing={actions.ltiKeysSetCustomizing}
dispatch={store.dispatch}
/>
)
}
return(
<NewKeyFooter
disable={this.isSaving}
onCancelClick={this.closeModal}
onSaveClick={this.submitForm}
/>

View File

@ -29,12 +29,12 @@ import PropTypes from 'prop-types'
export default class DeveloperKeyModalTrigger extends React.Component {
showCreateDeveloperKey = () => {
this.props.store.dispatch(this.props.actions.developerKeysModalOpen())
this.props.store.dispatch(this.props.actions.developerKeysModalOpen('api'))
}
showCreateLtiKey = () => {
this.props.store.dispatch(this.props.actions.ltiKeysSetLtiKey(true))
this.props.store.dispatch(this.props.actions.developerKeysModalOpen())
this.props.store.dispatch(this.props.actions.developerKeysModalOpen('lti'))
}
isLti13Enabled = ENV.LTI_1_3_ENABLED

View File

@ -76,3 +76,14 @@ it("Renders the 'Save' button if not customizing", () => {
.text()
).toEqual('Save Customizations')
})
it("Disables the save button if disable is true", () => {
wrapper = mount(<LtiKeyFooter {...newProps()} disable />)
expect(
wrapper
.find('Button')
.at(1)
.props()
.disabled
).toEqual(true)
})

View File

@ -181,8 +181,8 @@ actions.editDeveloperKey = payload => dispatch => {
}
actions.DEVELOPER_KEYS_MODAL_OPEN = 'DEVELOPER_KEYS_MODAL_OPEN'
actions.developerKeysModalOpen = () => {
window.location.hash = "key_modal_opened"
actions.developerKeysModalOpen = (type = 'api') => {
window.location.hash = `${type}_key_modal_opened`
return {type: actions.DEVELOPER_KEYS_MODAL_OPEN}
}

View File

@ -30,11 +30,15 @@ let state = store.getState()
*/
// ctx is context
function renderShowDeveloperKeys(ctx) {
if (ctx.hash === 'key_modal_opened') {
store.dispatch(actions.developerKeysModalOpen())
if (ctx.hash === 'api_key_modal_opened') {
store.dispatch(actions.developerKeysModalOpen('api'))
} else if (ctx.hash === 'lti_key_modal_opened') {
store.dispatch(actions.developerKeysModalOpen('lti'))
store.dispatch(actions.ltiKeysSetLtiKey(true))
} else {
store.dispatch(actions.developerKeysModalClose())
store.dispatch(actions.editDeveloperKey())
store.dispatch(actions.ltiKeysSetLtiKey(false))
}
if (!state.listDeveloperKeys.listDeveloperKeysSuccessful) {

View File

@ -400,7 +400,7 @@ describe 'Developer Keys' do
end
it "opens the developer key modal when open modal anchor is present" do
get "/accounts/#{Account.default.id}/developer_keys#key_modal_opened"
get "/accounts/#{Account.default.id}/developer_keys#api_key_modal_opened"
expect(find_button("Save Key")).to be_present
end