Repository: illinois/queue
Pull request #156 (2018-10-18 20:05:14)

[don't merge] JOSS Paper Submission WIP
This PR tracks our progress and revisions on our submission to JOSS.

For guidelines on our submission, see: https://joss.readthedocs.io/en/latest/submitting.html

See paper.md for the paper! :)

now[bot]: 
This pull request is [automatically deployed](https://zeit.co/docs/features/now-for-github?utm_source=automated&utm_medium=github&utm_campaign=now_bot) with [Now](https://zeit.co/now?utm_source=automated&utm_medium=github&utm_campaign=now_bot).
To access deployments, click Details below or on the icon next to each push.


	

Patch:
diff --git a/.prettierignore b/.prettierignore
index 13f5721..bfeb156 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -4,3 +4,4 @@
 package.json
 package-lock.json
 CHANGELOG.md
+README.md
diff --git a/API.md b/API.md
index a63a7ea..bccdb0d 100644
--- a/API.md
+++ b/API.md
@@ -1,29 +1,37 @@
 # Queue API
 
-### !!! Work in Progress !!!
-#### This isn't exactly complete yet. -Nathan
-
 There are a number of entities used by this application
 
-* `User`: Anyone using the service
-* `Course`: A course that can create queues. Many queues can be associated with each course.
-* `Queue`: A queue that accepts questions. Many questions can be associated with each queue.
-* `Question`: A question asked by a user.
+- `User`: Anyone using the service
+- `Course`: A course that can create queues. Many queues can be associated with each course.
+- `Queue`: A queue that accepts questions. Many questions can be associated with each queue.
+- `Question`: A question asked by a user.
 
 The following API routes are available to interact with these models. All paths
 described here are located under `/api/`.
 
-Route | Verbs | Description |
---- | --- | ---
-`/users` | `POST` | Create a new user
-`/users` | `GET` | Get all users
-`/users/:userId` | `GET`/`PATCH`/`DELETE` | Get/Update/Delete the given user
-`/courses` | `POST` | Create a new course
-`/courses` | `GET` | Get all courses
-`/courses/:courseId` | `GET`/`PATCH`/`DELETE` | Get/Update/Delete the given course
-`/courses/:courseId/queues` | `POST` | Create a new queue for the given course
-`/courses/:courseId/queues` | `GET` | Get all queues for the given course
-`/courses/:courseId/queues/:queueId` | `GET`/`PATCH`/`DELETE` | Get/Update/Delete the given queue
-`/courses/:courseId/queues/:queueId/questions` | `POST` | Create a question for the given queue
-`/courses/:courseId/queues/:queueId/questions` | `GET` | Get all questions for the queue
-`/courses/:courseId/queues/:queueId/questions/:questionId` | `GET`/`PATCH`/`DELETE` | Get/Update/Delete the given question
+| Route                              | Verbs                  | Description                                         |
+| ---------------------------------- | ---------------------- | --------------------------------------------------- |
+| `/users`                           | `POST`                 | Create a new user                                   |
+| `/users`                           | `GET`                  | Get all users                                       |
+| `/users/me`                        | `GET/PATCH`            | Get/Update the currently authenticated user         |
+| `/users/:userId`                   | `GET`/                 | Get the given user                                  |
+| `/courses`                         | `POST`                 | Create a new course                                 |
+| `/courses`                         | `GET`                  | Get all courses                                     |
+| `/courses/:courseId`               | `GET`                  | Get the given course                                |
+| `/courses/:courseId/staff`         | `POST`                 | Add someone to course staff                         |
+| `/courses/:courseId/staff/:userId` | `DELETE`               | Remove someone from course staff                    |
+| `/queues`                          | `POST`                 | Create a new queue for the given course             |
+| `/queues`                          | `GET`                  | Get all open queues                                 |
+| `/queues/:queueId`                 | `GET`/`PATCH`/`DELETE` | Get/Update/Delete the given queue                   |
+| `/queues/:queueId/staff`           | `GET`                  | Gets the on-duty staff list for a specific queue    |
+| `/queues/:queueId/staff/:userId`   | `POST`                 | Joins the specified user to the specified queue     |
+| `/queues/:queueId/staff/:userId`   | `DELETE`               | Removes the specified user from the specified queue |
+| `/questions`                       | `POST`                 | Adds a question to a queue                          |
+| `/questions`                       | `GET`                  | Get all questions for a particular queue            |
+| `/questions/:questionId`           | `GET`                  | Get a particular question                           |
+| `/questions/:questionId`           | `PATCH`                | Updates a question's information                    |
+| `/questions/:questionId`           | `DELETE`               | Deletes a question from a queue                     |
+| `/questions/:questionId/answering` | `POST`                 | Mark a question as being answered                   |
+| `/questions/:questionId/answering` | `DELETE`               | Mark a question as no longer being answered         |
+| `/questions/:questionId/answered`  | `POST`                 | Mark the question as answered                       |
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1fd3015..ae47bf0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,19 @@ with the current date and the next changes should go under a **[Next]** header.
 
 ## [Next]
 
+* Fix index route with empty base URL. ([@nwalters512](https://github.com/nwalters512) in [#160](https://github.com/illinois/queue/pull/160))
+* Allow an admin to join any course. ([@genevievehelsel](https://github.com/genevievehelsel) in [#164](https://github.com/illinois/queue/pull/164))
+* Updated API documentation. ([@genevievehelsel](https://github.com/genevievehelsel) in [#170](https://github.com/illinois/queue/pull/170))
+* Fix readme typo and prettierignore. ([@genevievehelsel](https://github.com/genevievehelsel) in [#159](https://github.com/illinois/queue/pull/159))
+* Sort courses in "Create a queue" panel. ([@ApoorvaDixit](https://github.com/ApoorvaDixit) in [#167](https://github.com/illinois/queue/pull/167))
+* Update README. ([@genevievehelsel](https://github.com/genevievehelsel) in [#171](https://github.com/illinois/queue/pull/171))
+
+## 22 October 2018
+
 * Log errors to `stdout` instead of `stderr`. ([@nwalters512](https://github.com/nwalters512) in [#152](https://github.com/illinois/queue/pull/152))
+* Add hack night advertisement banner. ([@genevievehelsel](https://github.com/genevievehelsel) in [#157](https://github.com/illinois/queue/pull/157))
+* Changed cancel question modal to contain clearer messages. ([@Xiangmingchen](https://github.com/Xiangmingchen) in [#141](https://github.com/illinois/queue/pull/141))
+* Remove slash from the index route. ([@nwalters512](https://github.com/nwalters512) in [#158](https://github.com/illinois/queue/pull/158))
 
 ## 11 October 2018
 
diff --git a/README.md b/README.md
index 1d9cada..0b6f409 100644
--- a/README.md
+++ b/README.md
@@ -2,31 +2,43 @@
 
 [![Build Status](https://travis-ci.org/illinois/queue.svg?branch=master)](https://travis-ci.org/illinois/queue)
 
-A micro-service queue for holding office hours.
+A micro-service queue for holding open office hours.
 
-Join the #queue-dev channel on the [UIUC OSS Slack team](https://illinois-oss.slack.com) to keep up with development!
+Join the #queue-general channel on the [UIUC OSS Slack team](https://illinois-oss.slack.com) to keep up with the queue!
 
 ## Using the Queue at Illinois
 
 At UIUC, this queue is hosted as a free service by Computer Science and EngrIT.
 
-* View the queue at https://edu.cs.illinois.edu/queue/
-* [Request to have a queue created for your course by filling out this short form](https://forms.illinois.edu/sec/691281)
+- View the queue at https://queue.illinois.edu/
+- [Request to have a queue created for your course by filling out this short form](https://forms.illinois.edu/sec/691281)
+
+## Contribution Guidelines
+
+- Join the #queue-dev channel on the [UIUC OSS Slack team](https://illinois-oss.slack.com) to seek development support
+- Claim an issue by commenting on an open issue in order to indicate to others that you are working on the task
+- Once you are ready to submit for review make sure to:
+  - Include a `CHANGELOG.md` entry following existing format
+  - If making API changes, note the change in `API.md`
+  - If your change requires new dependencies, add them to `package.json`
+  - Tag a lead developer in the reviewers section
+
+If you wish to report a bug, feature request, etc., please open a new issue (first checking that your issue has not already been filed).
 
 ## Running the Queue from Source
 
 ### Running locally in `dev` mode:
 
-* Clone the repository
-* Install [`node` and `npm`](https://nodejs.org/en/download/package-manager/)
-* Install dependencies: `npm install` in the cloned directory
-* Start the server: `npm run dev`
-  * Access the queue from a browser at `localhost:3000`
-  * You can run it on a different port by setting the `PORT` environment variable
+- Clone the repository
+- Install [`node` and `npm`](https://nodejs.org/en/download/package-manager/)
+- Install dependencies: `npm install` in the cloned directory
+- Start the server: `npm run dev`
+  - Access the queue from a browser at `localhost:3000`
+  - You can run it on a different port by setting the `PORT` environment variable
 
 ### Multiple users in dev mode
 
-In production, all auth is done my Shibboleth, and we just read out of the `eppn` header
+In production, all auth is done by Shibboleth, and we just read out of the `eppn` header
 to get a student's identity. In dev mode, we still want to be able to test with multiple
 users (for instance, to assert that user roles work correctly, or to ensue that syncing
 between multiple clients works correctly). To achieve that, we use `express-session` to
@@ -42,8 +54,8 @@ multiple incognito windows.
 
 Several configuration options are exposed via environment variables:
 
-* `PORT`: controls which port the app will be served from.
-* `BASE_URL`: allows the app to be served from somewhere other than the server
+- `PORT`: controls which port the app will be served from.
+- `BASE_URL`: allows the app to be served from somewhere other than the server
   root. This affects asset and API routes, websocket endpoints, generated links,
   and more. Note that for this to work effectively, the app must still receive
   the base URL as part of the request; this is important if the app is
diff --git a/paper.md b/paper.md
new file mode 100644
index 0000000..95c897c
--- /dev/null
+++ b/paper.md
@@ -0,0 +1,39 @@
+---
+title: "A micro-service digital queue with Shibboleth authentication" 
+tags:
+  - office hours
+  - queue
+  - nodejs
+authors:
+  - name: Genevieve Helsel
+    orcid: 0000-0003-2418-1566
+    affiliation: 1
+  - name: Nathan Walters
+    orcid: 0000-0002-5244-9155
+    affiliation: 1
+  - name: Wade Fagen-Ulmschnedier
+    orcid: 0000-0002-7313-7708
+    affiliation: 1
+  - name: Karle Flanagan
+    orcid: 0000-0002-2055-7573
+    affiliation: 2
+affiliations:
+ - name: Department of Computer Science, University of Illinois at Urbana-Champaign
+   index: 1
+ - name: Department of Statistics, University of Illinois at Urbana-Champaign
+   index: 2
+date: 26 October 2018
+#bibliography: paper.bib
+---
+
+# Summary
+
+University level courses offer office hours in which students can meet with instructors outside of scheduled class time to get additional help with course materials. Traditionally, these office hours are scheduled in a specific room in which an instructor or group of instructors move around a room and help students in a sporadic manner. While this model works for some courses, it is not one size fits all. Specifically, in courses with a large student to instructor ratio, this model gets too chaotic and difficult to handle, so a more structured and flexible solution is needed. 
+
+The Queue is a microservice used by courses to host open office hours. It was designed to be used by courses to remove themselves from the traditional office hours paradigm. It gives a more structured, organized, and simplified approach to office hours. Instead of hosting office hours in a specific room with an unstructured manner of answering questions, Instructors can open an online queue system for their course in which students then will add themselves to the queue of questions. Instructors then work down the list of students to answer questions. This allows for much flexibility, as office hours are no longer tied down to a specific location. Questions can be answered strictly in a first come-first serve basis. Students can also see how many questions are unanswered, allowing them to decide if they have the time to come to office hours without physically going there beforehand to get that information. From an analytics perspective, the queue can allow for a course to analyze data about their office hours, for example figuring out the busiest times and staffing those times accordingly.
+
+During the first year of using the queue, interesting uses have arisen from giving users access to a digital queuing tool.  Beyond large classrooms, instructors are using the queue as a means of facilitating active learning inside the classroom, advising offices are using it to organize and streamline drop-in advising, and courses are using the queue as a reservation system for one-on-one tutoring sessions.
+
+As the growth of the queue continues at the University of Illinois, several research projects are exploring the impact of the use of a digital queue in place of traditional office hours, the use of the queue in office hours, and interesting trends of data collected as part of the use of the queue in over nearly a dozen courses at Illinois.
+
+The source code for the Queue can be found at https://github.com/illinois/queue
diff --git a/src/components/ConfirmCancelQuestionModal.js b/src/components/ConfirmCancelQuestionModal.js
index 0915a03..e00cea5 100644
--- a/src/components/ConfirmCancelQuestionModal.js
+++ b/src/components/ConfirmCancelQuestionModal.js
@@ -7,8 +7,8 @@ const ConfirmCancelQuestionModal = props => (
     isOpen={props.isOpen}
     toggle={props.toggle}
     confirm={props.confirm}
-    descText="You will stop answering this question."
-    confirmText="Cancel question"
+    descText="Someone else is answering this question and you will interrupt it. This question will stay in the queue."
+    confirmText="Interrupt"
   />
 )
 
diff --git a/src/components/ConfirmModal.js b/src/components/ConfirmModal.js
index 0e398a0..78050a7 100644
--- a/src/components/ConfirmModal.js
+++ b/src/components/ConfirmModal.js
@@ -11,7 +11,7 @@ const ConfirmModal = props => (
         {props.confirmText}
       </Button>
       <Button color="secondary" onClick={props.toggle}>
-        Cancel
+        {props.optionalCancelText}
       </Button>
     </ModalFooter>
   </Modal>
@@ -23,6 +23,11 @@ ConfirmModal.propTypes = {
   confirm: PropTypes.func.isRequired,
   descText: PropTypes.string.isRequired,
   confirmText: PropTypes.string.isRequired,
+  optionalCancelText: PropTypes.string,
+}
+
+ConfirmModal.defaultProps = {
+  optionalCancelText: 'Cancel',
 }
 
 export default ConfirmModal
diff --git a/src/components/ConfirmStopAnsweringQuestionModal.js b/src/components/ConfirmStopAnsweringQuestionModal.js
new file mode 100644
index 0000000..f9115f4
--- /dev/null
+++ b/src/components/ConfirmStopAnsweringQuestionModal.js
@@ -0,0 +1,22 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import ConfirmModal from './ConfirmModal'
+
+const ConfirmStopAnsweringQuestionModal = props => (
+  <ConfirmModal
+    isOpen={props.isOpen}
+    toggle={props.toggle}
+    confirm={props.confirm}
+    descText="You will stop answering this question but it will stay in the queue."
+    confirmText="Stop Answering"
+    optionalCancelText="Resume Answering"
+  />
+)
+
+ConfirmStopAnsweringQuestionModal.propTypes = {
+  isOpen: PropTypes.bool.isRequired,
+  toggle: PropTypes.func.isRequired,
+  confirm: PropTypes.func.isRequired,
+}
+
+export default ConfirmStopAnsweringQuestionModal
diff --git a/src/components/DevWorkshopAd.js b/src/components/DevWorkshopAd.js
index 5843a81..ab885f4 100644
--- a/src/components/DevWorkshopAd.js
+++ b/src/components/DevWorkshopAd.js
@@ -4,19 +4,19 @@ import moment from 'moment'
 
 export default () => {
   // Hide ad once the event starts
-  if (moment().isAfter('2018-04-24 19:00:00.000-05')) return null
+  if (moment().isAfter('2018-10-24 18:00:00.000-05')) return null
 
   return (
     <Alert color="primary">
       <h6>Want to work on the queue?</h6>
       <p className="mb-0">
-        Join us for a development workshop on Tuesday, April 24th at 7PM in
-        Siebel 2405. We&apos;ll teach you how to run a version of the queue on
-        your machine, introduce you to the architecture, go over how to
-        contribute, and more!
+        Join us for a Hacktoberbest hack night on Wednesday, October 24th at 6PM
+        in Siebel 2405. We&apos;ll teach you how to run a version of the queue
+        on your machine, introduce you to the architecture, go over how to
+        contribute, and more. No prior experience needed!
         <a
           className="ml-2"
-          href="https://www.facebook.com/events/352213225287320/"
+          href="https://www.facebook.com/events/2192426457678107/"
         >
           More information
         </a>
diff --git a/src/components/NewQueue.js b/src/components/NewQueue.js
index b36b4c8..0fe51b7 100644
--- a/src/components/NewQueue.js
+++ b/src/components/NewQueue.js
@@ -232,7 +232,17 @@ NewQueue.propTypes = {
 
 const mapStateToProps = state => ({
   user: state.user.user,
-  courses: mapObjectToArray(state.courses.courses),
+  courses: mapObjectToArray(state.courses.courses).sort((a, b) => {
+    const x = a.name.toLowerCase()
+    const y = b.name.toLowerCase()
+    if (x < y) {
+      return -1
+    }
+    if (x > y) {
+      return 1
+    }
+    return 0
+  }),
 })
 
 export default connect(
diff --git a/src/components/Question.js b/src/components/Question.js
index e7531cb..e81b52a 100644
--- a/src/components/Question.js
+++ b/src/components/Question.js
@@ -29,20 +29,26 @@ class Question extends React.Component {
 
     let buttonCluster
     if (beingAnswered) {
-      if (isUserCourseStaff) {
+      if (isUserCourseStaff && isUserAnsweringQuestion) {
+        buttonCluster = (
+          <Fragment>
+            <Button
+              color="primary"
+              className="mr-2"
+              onClick={() => this.props.finishedAnswering(id)}
+            >
+              Finish Answering
+            </Button>
+            <Button color="light" onClick={() => this.props.cancelQuestion()}>
+              Stop Answering
+            </Button>
+          </Fragment>
+        )
+      } else if (isUserCourseStaff && !isUserAnsweringQuestion) {
         buttonCluster = (
           <Fragment>
-            {isUserAnsweringQuestion && (
-              <Button
-                color="primary"
-                className="mr-2"
-                onClick={() => this.props.finishedAnswering(id)}
-              >
-                Finish Answering
-              </Button>
-            )}
             <Button color="light" onClick={() => this.props.cancelQuestion()}>
-              Cancel
+              Interrupt
             </Button>
           </Fragment>
         )
diff --git a/src/components/QuestionList.js b/src/components/QuestionList.js
index f54c080..ac0e551 100644
--- a/src/components/QuestionList.js
+++ b/src/components/QuestionList.js
@@ -11,6 +11,7 @@ import QuestionFeedback from './QuestionFeedback'
 import ConfirmLeaveQueueModal from './ConfirmLeaveQueueModal'
 import ConfirmDeleteQuestionModal from './ConfirmDeleteQuestionModal'
 import ConfirmCancelQuestionModal from './ConfirmCancelQuestionModal'
+import ConfirmStopAnsweringQuestionModal from './ConfirmStopAnsweringQuestionModal'
 import QuestionEdit from './QuestionEdit'
 
 class QuestionList extends React.Component {
@@ -22,6 +23,7 @@ class QuestionList extends React.Component {
       showLeaveModal: false,
       showDeleteModal: false,
       showCancelModal: false,
+      showStopAnswerModal: false,
       showQuestionEditModal: false,
       attributeId: null,
       feedbackId: null,
@@ -117,11 +119,25 @@ class QuestionList extends React.Component {
     }))
   }
 
+  toggleStopAnswerModal() {
+    this.setState(prevState => ({
+      showStopAnswerModal: !prevState.showStopAnswerModal,
+    }))
+  }
+
   cancelQuestion(questionId) {
-    this.setState({
-      showCancelModal: true,
-      cancelId: questionId,
-    })
+    const question = this.props.questions[questionId]
+    if (this.props.userId === question.answeredById) {
+      this.setState({
+        showStopAnswerModal: true,
+        cancelId: questionId,
+      })
+    } else {
+      this.setState({
+        showCancelModal: true,
+        cancelId: questionId,
+      })
+    }
   }
 
   startQuestion(questionId) {
@@ -132,6 +148,7 @@ class QuestionList extends React.Component {
     this.props.updateQuestionBeingAnswered(this.state.cancelId, false)
     this.setState({
       showCancelModal: false,
+      showStopAnswerModal: false,
     })
   }
 
@@ -213,6 +230,11 @@ class QuestionList extends React.Component {
           toggle={() => this.toggleCancelModal()}
           confirm={() => this.handleConfirmedCancellation()}
         />
+        <ConfirmStopAnsweringQuestionModal
+          isOpen={this.state.showStopAnswerModal}
+          toggle={() => this.toggleStopAnswerModal()}
+          confirm={() => this.handleConfirmedCancellation()}
+        />
         <QuestionEdit
           question={this.props.questions[this.state.attributeId]}
           queue={this.props.queue}
diff --git a/src/components/StaffSidebar.js b/src/components/StaffSidebar.js
index 6d9a194..153f577 100644
--- a/src/components/StaffSidebar.js
+++ b/src/components/StaffSidebar.js
@@ -8,6 +8,7 @@ import { addQueueStaff, removeQueueStaff } from '../actions/queue'
 import {
   isUserCourseStaffForQueue,
   isUserActiveStaffForQueue,
+  isUserAdmin,
 } from '../selectors'
 import StaffMember from './StaffMember'
 
@@ -46,7 +47,7 @@ const StaffSidebar = props => {
   }
 
   let button = null
-  if (props.isUserCourseStaff) {
+  if (props.isUserCourseStaff || props.isUserAdmin) {
     if (props.isUserActiveStaff) {
       button = (
         <Button block color="danger" onClick={() => removeCurrentUser()}>
@@ -94,6 +95,7 @@ StaffSidebar.defaultProps = {
   activeStaff: {},
   isUserCourseStaff: false,
   isUserActiveStaff: false,
+  isUserAdmin: false,
 }
 
 StaffSidebar.propTypes = {
@@ -122,6 +124,7 @@ StaffSidebar.propTypes = {
   removeStaff: PropTypes.func.isRequired,
   isUserCourseStaff: PropTypes.bool,
   isUserActiveStaff: PropTypes.bool,
+  isUserAdmin: PropTypes.bool,
 }
 
 const mapStateToProps = (state, props) => ({
@@ -131,6 +134,7 @@ const mapStateToProps = (state, props) => ({
   queue: state.queues.queues[props.queueId],
   isUserCourseStaff: isUserCourseStaffForQueue(state, props),
   isUserActiveStaff: isUserActiveStaffForQueue(state, props),
+  isUserAdmin: isUserAdmin(state),
 })
 
 const mapDispatchToProps = (dispatch, ownProps) => ({
diff --git a/src/routes.js b/src/routes.js
index 5b05239..8523592 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -1,8 +1,16 @@
 const routes = require('next-routes')()
-const { withBaseUrl } = require('./util')
+const { baseUrl, withBaseUrl } = require('./util')
 
+// We need to special-case the index route. When running with a non-empty
+// base URL, for instance /foo, we'll fail to resolve /foo if the index URL
+// ends with a slash. However, when running locally with an empty base URL,
+// we need the root route to be "/" for it to be resolved properly.
+if (baseUrl === '') {
+  routes.add('index', '/')
+} else {
+  routes.add('index', withBaseUrl(''))
+}
 routes
-  .add('index', withBaseUrl('/'))
   .add('queue', withBaseUrl('/queue/:id'))
   .add('createCourse', withBaseUrl('/course/create'))
   .add('course', withBaseUrl('/course/:id'))


Test patch:
diff --git a/src/components/Question.test.js b/src/components/Question.test.js
index df5e86b..3794ad8 100644
--- a/src/components/Question.test.js
+++ b/src/components/Question.test.js
@@ -123,10 +123,10 @@ describe('<Question />', () => {
     expect(findButton(wrapper, 'Delete')).toHaveLength(1)
   })
 
-  test('hides "Finish Answering" and "Cancel" buttons for standard user', () => {
+  test('hides "Finish Answering" and "Stop Answering" buttons for standard user', () => {
     const wrapper = makeWrapper({ beingAnswered: true })
     expect(findButton(wrapper, 'Finish Answering')).toHaveLength(0)
-    expect(findButton(wrapper, 'Cancel')).toHaveLength(0)
+    expect(findButton(wrapper, 'Stop Answering')).toHaveLength(0)
   })
 
   test('shows "Cancel" button for course staff when being answered', () => {
@@ -134,7 +134,7 @@ describe('<Question />', () => {
       beingAnswered: true,
       isUserCourseStaff: true,
     })
-    expect(findButton(wrapper, 'Cancel')).toHaveLength(1)
+    expect(findButton(wrapper, 'Interrupt')).toHaveLength(1)
   })
 
   test('hides "Finish Answering" button for other course staff when being answered', () => {
@@ -145,14 +145,14 @@ describe('<Question />', () => {
     expect(findButton(wrapper, 'Finish Answering')).toHaveLength(0)
   })
 
-  test('shows "Finish Answering" and "Cancel" for course staff who is answering question', () => {
+  test('shows "Finish Answering" and "Stop Answering" for course staff who is answering question', () => {
     const wrapper = makeWrapper({
       beingAnswered: true,
       isUserCourseStaff: true,
       isUserAnsweringQuestion: true,
     })
     expect(findButton(wrapper, 'Finish Answering')).toHaveLength(1)
-    expect(findButton(wrapper, 'Cancel')).toHaveLength(1)
+    expect(findButton(wrapper, 'Stop Answering')).toHaveLength(1)
   })
 
   test('shows name of person answering question', () => {