Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debounced event logging #1245

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 7 additions & 29 deletions assets/src/components/AssignmentGoalInput.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import React, { useEffect, useState, useRef } from 'react'
import React from 'react'
import { withStyles } from '@material-ui/core'
import Grid from '@material-ui/core/Grid'
import Button from '@material-ui/core/Button'
import StyledTextField from './StyledTextField'
import debounce from 'lodash.debounce'
import { eventLogExtra } from '../util/object'

const styles = ({
goalGradeInput: {
Expand All @@ -15,40 +13,20 @@ const styles = ({

function AssignmentGoalInput (props) {
const {
currentGrade,
maxPossibleGrade,
goalGrade,
setGoalGrade,
setEventLog,
eventLog,
handleClearGoalGrades,
mathWarning,
classes
} = props

const [goalGradeInternal, setGoalGradeInternal] = useState(goalGrade)
const debouncedGoalGrade = useRef(debounce(q => setGoalGrade(q), 500)).current
const updateGoalGradeInternal = (grade) => {
const v = { courseGoalGrade: grade }
if (goalGrade !== '') {
// only send prev grade object when there is previous value
v.prevCourseGoalGrade = goalGrade
}
setEventLog(eventLogExtra(v, eventLog, currentGrade, maxPossibleGrade))
debouncedGoalGrade(grade)
setGoalGradeInternal(grade)
}

useEffect(() => {
setGoalGradeInternal(goalGrade)
}, [goalGrade])

return (
<Grid item>
<StyledTextField
error={goalGradeInternal > 100 || mathWarning || goalGradeInternal > maxPossibleGrade}
error={goalGrade > 100 || mathWarning || goalGrade > maxPossibleGrade}
id='standard-number'
value={goalGradeInternal}
value={goalGrade}
label={
mathWarning
? 'Scores no longer match goal'
Expand All @@ -61,13 +39,13 @@ function AssignmentGoalInput (props) {
onChange={event => {
const goalGrade = event.target.value
if (goalGrade === '') {
updateGoalGradeInternal('')
setGoalGrade('')
} else if (goalGrade <= 0) {
updateGoalGradeInternal(0)
setGoalGrade(0)
} else if (goalGrade > 125) {
updateGoalGradeInternal(125)
setGoalGrade(125)
} else {
updateGoalGradeInternal(goalGrade)
setGoalGrade(goalGrade)
}
}}
type='number'
Expand Down
50 changes: 11 additions & 39 deletions assets/src/components/AssignmentTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ import SubmittedIcon from '@material-ui/icons/Textsms'
import ProgressBarV2 from './ProgressBarV2'
import PopupMessage from './PopupMessage'
import ConditionalWrapper from './ConditionalWrapper'
import StyledTextField from './StyledTextField'
import AlertBanner from '../components/AlertBanner'
import usePopoverEl from '../hooks/usePopoverEl'
import { calculateWeekOffset } from '../util/date'
import { roundToXDecimals, getDecimalPlaceOfFloat } from '../util/math'
import { roundToXDecimals } from '../util/math'
import { assignmentStatus } from '../util/assignment'
import GoalInput from './GoalInput'

const headerHeight = 105

Expand Down Expand Up @@ -110,9 +110,7 @@ function AssignmentTable (props) {
assignmentGroups,
dateStart,
handleAssignmentGoalGrade,
handleAssignmentLock,
handleInputFocus,
handleInputBlur
handleAssignmentLock
} = props

const assignmentStatusNames = Object.values(assignmentStatus)
Expand All @@ -122,24 +120,16 @@ function AssignmentTable (props) {
current state of the various AssignmentTable filters.
*/
const [filteredAssignments, setFilteredAssignments] = useState(assignments)

const [popoverEl, setPopoverEl, clearPopoverEl] = usePopoverEl()

const [assignmentGroupNames, setAssignmentGroupNames] = useState([])

const [assignmentNameFilter, setAssignmentNameFilter] = useState('')

const [assignmentGroupFilterArray, setAssignmentGroupFilterArray] = useState([])

const [assignmentStatusFilterArray, setAssignmentStatusFilterArray] = useState([])

const [filtersAreClear, setFiltersAreClear] = useState(true)

const [previousWeek, setPreviousWeek] = useState()

const weeksPresent = useRef([])
const shouldScrollToCurrentWeek = useRef(false)

const tableRef = useRef(null)
const previousWeekRow = useRef(null)

Expand Down Expand Up @@ -174,10 +164,6 @@ function AssignmentTable (props) {
: false
}

// Use decimal place of pointsPossible if it's a decimal; otherwise, round to nearest tenth
const placeToRoundTo = pointsPossible => (String(pointsPossible).includes('.'))
? getDecimalPlaceOfFloat(pointsPossible) : 1

// this effect scrolls to current week of assignments if it exists
useEffect(() => {
if (!shouldScrollToCurrentWeek.current && previousWeekRow.current) {
Expand Down Expand Up @@ -411,25 +397,11 @@ function AssignmentTable (props) {
a.graded || a.outOf === 0
? <div className={classes.possiblePointsText}>{a.outOf === 0 ? '0' : `${a.currentUserSubmission.score}`}</div>
: (
<StyledTextField
error={(a.goalGrade / a.pointsPossible) > 1}
<GoalInput
disabled={!courseGoalGradeSet}
id='standard-number'
value={roundToXDecimals(a.goalGrade, placeToRoundTo(a.pointsPossible))}
label={
!courseGoalGradeSet ? 'Set a goal'
: (a.goalGrade / a.pointsPossible) > 1
? 'Over 100%'
: 'Set a goal'
}
onChange={event => {
const assignmentGoalGrade = event.target.value
handleAssignmentGoalGrade(a.id, assignmentGoalGrade, a.goalGrade)
}}
type='number'
className={classes.goalGradeInput}
onFocus={() => handleInputFocus(a.id)}
onBlur={() => handleInputBlur(a.id)}
goalGrade={a.goalGrade}
pointsPossible={a.pointsPossible}
handleAssignmentGoalGrade={handleAssignmentGoalGrade(a.id)}
/>
)
}
Expand All @@ -454,11 +426,11 @@ function AssignmentTable (props) {
? [{ color: 'green', value: a.goalGrade, draggable: true }]
: []
}
description={`This assignment is worth ${a.percentOfFinalGrade}% of your grade.
description={`This assignment is worth ${a.percentOfFinalGrade}% of your grade.
Points possible: ${a.pointsPossible}.
Your goal: ${(a.goalGrade ? a.goalGrade : 'None')}.
Your grade: ${(a.grade ? a.grade : 'Not graded')}.
Class average: ${a.averageGrade}.
Your goal: ${(a.goalGrade ? a.goalGrade : 'None')}.
Your grade: ${(a.grade ? a.grade : 'Not graded')}.
Class average: ${a.averageGrade}.
Rules: ${(a.rules ? a.rules : 'There are no rules for this assignment')}. `}
onBarFocus={el => setPopoverEl(key, el)}
onBarBlur={clearPopoverEl}
Expand Down
53 changes: 53 additions & 0 deletions assets/src/components/GoalInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react'
import PropTypes from 'prop-types'
import StyledTextField from './StyledTextField'
import { placeToRoundTo, roundToXDecimals } from '../util/math'
import { withStyles } from '@material-ui/core/styles'

const styles = theme => ({
goalGradeInput: {
marginTop: 0,
width: 100,
marginBottom: '10px'
}
})

function GoalInput (props) {
const {
classes,
goalGrade,
pointsPossible,
disabled,
handleAssignmentGoalGrade
} = props

return (
<StyledTextField
error={(goalGrade / pointsPossible) > 1}
disabled={disabled}
id='standard-number'
value={roundToXDecimals(goalGrade, placeToRoundTo(pointsPossible))}
label={
!disabled ? 'Set a goal'
: (goalGrade / pointsPossible) > 1
? 'Over 100%'
: 'Set a goal'
}
onChange={event => {
const newGoalGrade = event.target.value
handleAssignmentGoalGrade(newGoalGrade, goalGrade)
}}
type='number'
className={classes.goalGradeInput}
/>
)
}

GoalInput.propTypes = {
goalGrade: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
pointsPossible: PropTypes.number // seems like it should be required
}

GoalInput.defaultProps = {}

export default withStyles(styles)(GoalInput)
50 changes: 19 additions & 31 deletions assets/src/containers/AssignmentPlanningV2.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@ import useSyncAssignmentAndGoalGrade from '../hooks/useSyncAssignmentAndGoalGrad
import useUserAssignmentSetting from '../hooks/useUserAssignmentSetting'
import { isTeacherOrAdmin } from '../util/roles'
import { Helmet } from 'react-helmet'
import { eventLogExtra } from '../util/object'
import { createEventLog } from '../util/object'
import { roundToXDecimals } from '../util/math'

import {
assignmentStatus,
clearGoals,
setAssignmentGoalGrade,
setAssignmentGoalLockState,
setAssigmentGoalInputState
setAssignmentGoalLockState
} from '../util/assignment'

const styles = theme => ({
Expand Down Expand Up @@ -124,22 +122,32 @@ function AssignmentPlanningV2 (props) {
settingChanged
})

const handleAssignmentGoalGrade = (key, assignmentGoalGrade, prevGoalGrade) => {
const handleGoalGrade = (goalGrade, prevGoalGrade) => {
const v = { courseGoalGrade: goalGrade }
if (goalGrade !== '') {
v.prevCourseGoalGrade = prevGoalGrade
}
setEventLog(createEventLog(v, eventLog, currentGrade, maxPossibleGrade))
setSettingChanged(true)
setGoalGrade(goalGrade)
}

const handleAssignmentGoalGrade = key => (goalGrade, prevGoalGrade) => {
const v = {
assignmentId: key,
assignGoalGrade: assignmentGoalGrade,
assignGoalGrade: goalGrade,
assignPrevGoalGrade: roundToXDecimals(prevGoalGrade, 1)
}
setEventLog(eventLogExtra(v, eventLog, currentGrade, maxPossibleGrade))
setEventLog(createEventLog(v, eventLog, currentGrade, maxPossibleGrade))
setSettingChanged(true)
setAssignments(
setAssignmentGoalGrade(key, assignments, assignmentGoalGrade)
setAssignmentGoalGrade(key, assignments, goalGrade)
)
}

const handleClearGoalGrades = () => {
const v = { courseGoalGrade: '', prevCourseGoalGrade: goalGrade }
setEventLog(eventLogExtra(v, eventLog, currentGrade, maxPossibleGrade))
setEventLog(createEventLog(v, eventLog, currentGrade, maxPossibleGrade))
setAssignments(clearGoals(assignments))
setGoalGrade('')
setSettingChanged(true)
Expand All @@ -148,24 +156,12 @@ function AssignmentPlanningV2 (props) {
const handleAssignmentLock = (key, checkboxState) => {
const assignment = assignments.filter(a => a.id === key)
const v = { assignmentId: key, assignGoalGrade: roundToXDecimals(assignment[0].goalGrade, 1), checkboxLockState: checkboxState }
setEventLog(eventLogExtra(v, eventLog, currentGrade, maxPossibleGrade))
setEventLog(createEventLog(v, eventLog, currentGrade, maxPossibleGrade))
setAssignments(
setAssignmentGoalLockState(key, assignments, checkboxState)
)
}

const handleInputFocus = key => {
setAssignments(
setAssigmentGoalInputState(key, assignments, true)
)
}

const handleInputBlur = key => {
setAssignments(
setAssigmentGoalInputState(key, assignments, false)
)
}

if (error) return (<WarningBanner />)

return (
Expand All @@ -190,13 +186,7 @@ function AssignmentPlanningV2 (props) {
goalGrade={goalGrade}
maxPossibleGrade={maxPossibleGrade}
eventLog={eventLog}
setEventLog={eventLog => {
setEventLog(eventLog)
}}
setGoalGrade={grade => {
setSettingChanged(true)
setGoalGrade(grade)
}}
setGoalGrade={newGoalGrade => handleGoalGrade(newGoalGrade, goalGrade)}
handleClearGoalGrades={handleClearGoalGrades}
mathWarning={showMathWarning}
/>
Expand Down Expand Up @@ -258,8 +248,6 @@ function AssignmentPlanningV2 (props) {
dateStart={data.course.dateStart}
handleAssignmentGoalGrade={handleAssignmentGoalGrade}
handleAssignmentLock={handleAssignmentLock}
handleInputFocus={handleInputFocus}
handleInputBlur={handleInputBlur}
/>
</div>
)
Expand Down
7 changes: 5 additions & 2 deletions assets/src/hooks/useSaveUserSetting.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { useEffect } from 'react'
import { useEffect, useRef } from 'react'
import useSetUserSettingGQL from './useSetUserSettingGQL'
import { createUserSettings } from '../util/assignment'
import { loadedWithoutError } from '../util/data'
import debounce from 'lodash.debounce'

const useSaveUserSetting = ({ loading, error, courseId, userSetting, settingChanged }) => {
const { saveUserSetting, mutationLoading, mutationError } = useSetUserSettingGQL()

const debouncedSave = useRef(debounce(userSetting => saveUserSetting(userSetting), 1000)).current

useEffect(() => {
if (loadedWithoutError(loading, error) && settingChanged) {
saveUserSetting(
debouncedSave(
createUserSettings(courseId, 'assignment', userSetting)
)
}
Expand Down
6 changes: 3 additions & 3 deletions assets/src/util/assignment.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ const setAssigmentGoalInputState = (assignmentId, assignments, inputFocus) => {
}

const setAssignmentGoalGrade = (assignmentId, assignments, goalGrade) => {
const assignment = assignments.filter(a => a.id === assignmentId)
if (assignment.length !== 1) {
const assignment = assignments.filter(a => a.id === assignmentId)[0]
if (!assignment) {
console.error('Error finding unique assignment id')
return assignments
} else {
const key = assignments.indexOf(assignment[0])
const key = assignments.indexOf(assignment)
return [
...assignments.slice(0, key),
{
Expand Down
7 changes: 6 additions & 1 deletion assets/src/util/math.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,17 @@ const isOutOfRange = (data, checkPointData) => {

const sum = arr => arr.reduce((acc, cur) => (acc += cur), 0)

// Use decimal place of pointsPossible if it's a decimal; otherwise, round to nearest tenth
const placeToRoundTo = pointsPossible => (String(pointsPossible).includes('.'))
? getDecimalPlaceOfFloat(pointsPossible) : 1

export {
average,
getDecimalPlaceOfFloat,
median,
roundToXDecimals,
sum,
isInRange,
isOutOfRange
isOutOfRange,
placeToRoundTo
}
Loading