Skip to content

Commit

Permalink
feat(app): Add upload protocol warning modal (#1988)
Browse files Browse the repository at this point in the history
closes #1032
  • Loading branch information
Kadee80 authored Aug 3, 2018
1 parent d19d29b commit 8e010cf
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 67 deletions.
2 changes: 1 addition & 1 deletion api/opentrons/api/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def create(self, name, text):

def clear(self):
if self.session:
self.session.refresh()
robot.reset()
self.session = None

def get_session(self):
Expand Down
38 changes: 38 additions & 0 deletions app/src/components/UploadAlert/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// @flow
import React from 'react'
import {connect} from 'react-redux'
import {goBack} from 'react-router-redux'
import {AlertModal} from '@opentrons/components'

import {
actions as robotActions
} from '../../robot'

type Props = {
cancelUpload: () => mixed,
confirmUpload: () => mixed,
}

export default connect(null, mapDTP)(ConfirmUploadModal)

function ConfirmUploadModal (props: Props) {
return (
<AlertModal
heading={'Are you sure you want to open a new protocol?'}
buttons={[
{children: 'cancel', onClick: props.cancelUpload},
{children: 'continue', onClick: props.confirmUpload}
]}
alertOverlay
>
Doing so will close your current protocol and clear any unsaved calibration progress.
</AlertModal>
)
}

function mapDTP (dispatch) {
return {
cancelUpload: () => dispatch(goBack()),
confirmUpload: () => dispatch(robotActions.clearSession())
}
}
61 changes: 61 additions & 0 deletions app/src/components/UploadPanel/Upload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// @flow
import * as React from 'react'
import UploadInput from './UploadInput'

type Props = {
sessionLoaded: ?boolean,
createSession: (file: File) => mixed,
confirmUpload: () => mixed,
}

type State = {
uploadedFile: ?File,
}

export default class Upload extends React.Component<Props, State> {
constructor (props: Props) {
super(props)
this.state = {
uploadedFile: null
}
}

onUpload = (
event: SyntheticInputEvent<HTMLInputElement> | SyntheticDragEvent<*>
) => {
let files: Array<File> = []
if (event.dataTransfer && event.dataTransfer.files) {
files = (event.dataTransfer.files: any)
} else if (event.target.files) {
files = (event.target.files: any)
}

if (this.props.sessionLoaded) {
this.setState({uploadedFile: files[0]})
this.props.confirmUpload()
} else {
this.props.createSession(files[0])
}

// $FlowFixMe
event.target.value = null
}

// TODO (ka 2018-7-30): refactor to consider edge case where robot disconnects while on upload page
componentDidUpdate () {
const {uploadedFile} = this.state
if (uploadedFile && !this.props.sessionLoaded) {
this.props.createSession(uploadedFile)
this.setState({uploadedFile: null})
}
}

render () {
return (
<React.Fragment>
<UploadInput onUpload={this.onUpload} isButton />
<UploadInput onUpload={this.onUpload} />
</React.Fragment>
)
}
}
2 changes: 1 addition & 1 deletion app/src/components/UploadPanel/UploadInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {PrimaryButton, Icon} from '@opentrons/components'
import styles from './upload-panel.css'

type Props = {
onUpload: (SyntheticEvent<>) => void,
onUpload: (SyntheticInputEvent<HTMLInputElement> | SyntheticDragEvent<*>) => void,
isButton?: boolean
}

Expand Down
17 changes: 0 additions & 17 deletions app/src/components/UploadPanel/UploadWarning.js

This file was deleted.

43 changes: 13 additions & 30 deletions app/src/components/UploadPanel/index.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,41 @@
// @flow
import React from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {push} from 'react-router-redux'

import {
actions as robotActions,
selectors as robotSelectors
} from '../../robot'

import {SidePanel} from '@opentrons/components'
import UploadInput from './UploadInput'
import UploadWarning from './UploadWarning'
import Upload from './Upload'

export default connect(mapStateToProps, mapDispatchToProps)(UploadPanel)

UploadPanel.propTypes = {
isSessionLoaded: PropTypes.bool.isRequired,
onUpload: PropTypes.func.isRequired
type Props = {
sessionLoaded: ?boolean,
confirmUpload: () => mixed,
createSession: () => mixed,
}

function UploadPanel (props) {
const warning = props.isSessionLoaded && (<UploadWarning />)
export default connect(mapStateToProps, mapDispatchToProps)(UploadPanel)

function UploadPanel (props: Props) {
return (
<SidePanel title='Open Protocol'>
<div>
<UploadInput {...props} isButton />
<UploadInput {...props} />
{warning}
</div>
<Upload {...props} />
</SidePanel>
)
}

function mapStateToProps (state) {
return {
isSessionLoaded: robotSelectors.getSessionIsLoaded(state)
sessionLoaded: robotSelectors.getSessionIsLoaded(state)
}
}

function mapDispatchToProps (dispatch) {
return {
onUpload: (event) => {
let files

if (event.dataTransfer) {
files = event.dataTransfer.files
} else {
files = event.target.files
}

dispatch(robotActions.session(files[0]))

// reset the state of the input to allow file re-uploads
event.target.value = null
}
confirmUpload: () => dispatch(push('/upload/confirm')),
createSession: (file) => dispatch(robotActions.session(file))
}
}
15 changes: 0 additions & 15 deletions app/src/components/UploadPanel/upload-panel.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,3 @@
margin: 3rem auto;
width: 4rem;
}

.upload_warning {
margin: 1rem;
text-transform: uppercase;
}

.upload_warning_title {
@apply --font-header-dark;

margin-bottom: 0.5rem;
}

.upload_warning_body {
@apply --font-body-2-dark;
}
12 changes: 10 additions & 2 deletions app/src/pages/Upload.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
// @flow
import React from 'react'

import {Route, type Match} from 'react-router'
import UploadStatus from '../components/UploadStatus'
import UploadAlert from '../components/UploadAlert'
import Page from '../components/Page'

export default function UploadPage () {
type Props = {
match: Match
}

export default function UploadPage (props: Props) {
const {match: {path}} = props
return (
<Page>
<UploadStatus />
<Route exact path={`${path}/confirm`} component={UploadAlert} />
</Page>
)
}
12 changes: 12 additions & 0 deletions app/src/robot/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ export type RefreshSessionAction = {|
|},
|}

export type ClearSessionAction = {|
type: 'robot:CLEAR_SESSION',
meta: {|
robotCommand: true
|},
|}

export type CalibrationResponseAction =
| CalibrationSuccessAction
| CalibrationFailureAction
Expand Down Expand Up @@ -226,6 +233,7 @@ export type Action =
| SessionUpdateAction
| RefreshSessionAction
| SetModulesReviewedAction
| ClearSessionAction

export const actions = {
discover (): DiscoverAction {
Expand Down Expand Up @@ -299,6 +307,10 @@ export const actions = {
}
},

clearSession () {
return tagForRobotApi({type: 'robot:CLEAR_SESSION'})
},

setModulesReviewed (payload: boolean) {
return {type: 'robot:SET_MODULES_REVIEWED', payload}
},
Expand Down
7 changes: 7 additions & 0 deletions app/src/robot/api-client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default function client (dispatch) {
case actionTypes.RESUME: return resume(state, action)
case actionTypes.CANCEL: return cancel(state, action)
case 'robot:REFRESH_SESSION': return refreshSession(state, action)
case 'robot:CLEAR_SESSION': return clearSession(state, action)
}
}

Expand Down Expand Up @@ -286,6 +287,12 @@ export default function client (dispatch) {
.catch((error) => dispatch(actions.sessionResponse(error)))
}

function clearSession (state, action) {
remote.session_manager.clear()
.catch((error) => console.warn('error clearing session', error))
.then(() => (remote.session_manager.session = null))
}

function setRunTimerInterval () {
if (runTimerInterval === NO_INTERVAL) {
runTimerInterval = setInterval(
Expand Down
13 changes: 12 additions & 1 deletion app/src/robot/reducer/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {actionTypes} from '../actions'
import type {
Action,
DisconnectResponseAction,
SessionUpdateAction
SessionUpdateAction,
ClearSessionAction
} from '../actions'

type Request = {
Expand Down Expand Up @@ -101,6 +102,9 @@ export default function sessionReducer (
case 'robot:SESSION_UPDATE':
return handleSessionUpdate(state, action)

case 'robot:CLEAR_SESSION':
return handleClearSession(state, action)

case SESSION:
case 'robot:REFRESH_SESSION':
return handleSession(state, action)
Expand All @@ -127,6 +131,13 @@ function handleDisconnectResponse (
return INITIAL_STATE
}

function handleClearSession (
state: State,
action: ClearSessionAction
): State {
return INITIAL_STATE
}

function handleSessionUpdate (
state: State,
action: SessionUpdateAction
Expand Down
9 changes: 9 additions & 0 deletions app/src/robot/test/actions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@ describe('robot actions', () => {
expect(actions.sessionUpdate(update)).toEqual(expected)
})

test('CLEAR_SESSION action', () => {
const expected = {
type: 'robot:CLEAR_SESSION',
meta: {robotCommand: true}
}

expect(actions.clearSession()).toEqual(expected)
})

test('set modules reviewed action', () => {
expect(actions.setModulesReviewed(false)).toEqual({
type: 'robot:SET_MODULES_REVIEWED',
Expand Down
30 changes: 30 additions & 0 deletions app/src/robot/test/session-reducer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,36 @@ describe('robot reducer - session', () => {
})
})

test('handles robot:CLEAR_SESSION action', () => {
const state = {
session: {
sessionRequest: {inProgress: false, error: null},
startTime: 40,
runTime: 42
}
}
const action = {type: 'robot:CLEAR_SESSION'}

expect(reducer(state, action).session).toEqual({
sessionRequest: {inProgress: false, error: null},
name: '',
state: '',
errors: [],
protocolText: '',
protocolCommands: [],
protocolCommandsById: {},
pipettesByMount: {},
labwareBySlot: {},
modulesBySlot: {},
runRequest: {inProgress: false, error: null},
pauseRequest: {inProgress: false, error: null},
resumeRequest: {inProgress: false, error: null},
cancelRequest: {inProgress: false, error: null},
startTime: null,
runTime: 0
})
})

test('handles SESSION_RESPONSE success', () => {
const state = {
session: {
Expand Down

0 comments on commit 8e010cf

Please sign in to comment.