-
Notifications
You must be signed in to change notification settings - Fork 10
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
107 bulk enroll um user #177
Merged
chrisrrowland
merged 56 commits into
tl-its-umich-edu:main
from
chrisrrowland:107_bulk_enroll_um_user
Aug 31, 2021
Merged
Changes from all commits
Commits
Show all changes
56 commits
Select commit
Hold shift + click to select a range
8696892
Creatated AddUMUsers page w/ stepper
chrisrrowland 7372417
Move step content to functions
chrisrrowland 44f3d51
Wire up get course sections w/ create sections FE
chrisrrowland ad43a0e
Merge upstream branch into main
chrisrrowland 625a950
Merge branch 'main' into 107_bulk_enroll_um_user
chrisrrowland f9e14e4
Added functionless CreateSectionWidget
chrisrrowland 9bb375f
Start of a section selector
chrisrrowland 4f15988
Small layout correction
chrisrrowland b509718
Loading spinner and selection callback function for section list
chrisrrowland 43e6778
Sort section list alphabetically
chrisrrowland dc09591
Work in progress. Create section widget
chrisrrowland 7a362ae
Enter keys to create section
chrisrrowland f40682f
Fix a typescript warning.
chrisrrowland a9697fc
making tot_student field mandatory (#4)
pushyamig 22baa16
Starting work on upload section
chrisrrowland 2c55827
Merge branch '107_bulk_enroll_um_user' of https://github.com/chrisrro…
chrisrrowland 7681141
need a minHeight so backdrop has somewhere to go
chrisrrowland 83370d9
Initial work with confirmation
chrisrrowland 9b176a0
Handle parse errors
chrisrrowland 372aa34
Show an error if can't load sections from canvas
chrisrrowland f70a79d
Fix regex
chrisrrowland 17f2221
Multiselect support
chrisrrowland 5b9b228
Get rid of some unnecessary state
chrisrrowland 78d770d
Select newly created section
chrisrrowland 3328e6b
Changed uniqname references
chrisrrowland d82d2d8
Merge branch 'main' into 107_bulk_enroll_um_user
chrisrrowland 6916160
Make cancel button functional
chrisrrowland 63177d7
remove some log statements
chrisrrowland 3c1ccc5
Preemptive assault on variable names
chrisrrowland 8ae9f7e
Fix error icon color
chrisrrowland 3a81cc0
Added missing newline
chrisrrowland 56ef565
Search within sections
chrisrrowland cb4a9f4
Add clear button
chrisrrowland 2599008
A little room to breathe above section selctor
chrisrrowland de43bad
Cleaner clear button on section search
chrisrrowland b6a212b
Fix Role being labled as Section Name
chrisrrowland 397c243
Section subtext 'students' instead of 'users'
chrisrrowland 23a5240
Add a small margin below section list
chrisrrowland 961d4ff
Don't sort enrollments
chrisrrowland ca0c984
Fix wrong line number in error
chrisrrowland f2d8e17
remove log statement
chrisrrowland af0c7bb
Fix floating swagger link
chrisrrowland c1f0b29
Fix non functional spelling error
chrisrrowland 4180339
Use enums in switch statement.
chrisrrowland cf854b5
Remove a comment
chrisrrowland 2e82a29
change bulk um enroll example file name
chrisrrowland 79f8f3d
never ever have a line of currently unreferenced code
chrisrrowland adb5d08
Spacing between section name input and button
chrisrrowland 7caf827
Change hint text
chrisrrowland 6cc9179
Add helpful tooltip
chrisrrowland 278400d
Make 'Or select one available section to add users' farther away
chrisrrowland 45854c8
improve responsiveness of create section widget
chrisrrowland 6a2abdf
New tooltip text
chrisrrowland 485fcb9
Change the name again. I'm sure it wont change again 🤪
chrisrrowland bb3df7e
Toast on parsing error
chrisrrowland 477e774
So I can put this back in again later
chrisrrowland File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
ccm_web/client/src/components/BulkEnrollUMUserConfirmationTable.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import React, { useState } from 'react' | ||
import ConfirmationTable from './ConfirmationTable' | ||
|
||
interface IAddUMUserEnrollment { | ||
rowNumber: number | ||
loginID: string | ||
role: string | ||
} | ||
|
||
interface BulkEnrollUMUserConfirmationTableProps { | ||
enrollments: IAddUMUserEnrollment[] | ||
} | ||
|
||
interface TableHeaderColumnInfoShouldUseMatUIType { | ||
id: keyof IAddUMUserEnrollment | ||
label: string | ||
minWidth: number | ||
align?: 'left' | 'right' | undefined | ||
} | ||
|
||
const columns: TableHeaderColumnInfoShouldUseMatUIType[] = [ | ||
{ id: 'rowNumber', label: 'Row Number', minWidth: 25 }, | ||
{ id: 'loginID', label: 'Login ID', minWidth: 100 }, | ||
{ id: 'role', label: 'Role', minWidth: 100 } | ||
] | ||
|
||
function BulkEnrollUMUserConfirmationTable (props: BulkEnrollUMUserConfirmationTableProps): JSX.Element { | ||
const [page, setPage] = useState<number>(0) | ||
|
||
const tableRows = props.enrollments | ||
|
||
return <ConfirmationTable<IAddUMUserEnrollment> {...{ tableRows, columns, page, setPage }} /> | ||
} | ||
|
||
export type { BulkEnrollUMUserConfirmationTableProps, IAddUMUserEnrollment } | ||
export default BulkEnrollUMUserConfirmationTable |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import { Button, Grid, makeStyles, TextField } from '@material-ui/core' | ||
import React, { ChangeEvent, useState } from 'react' | ||
import { useSnackbar } from 'notistack' | ||
import { addCourseSections } from '../api' | ||
import { CanvasCourseSection } from '../models/canvas' | ||
import { CCMComponentProps } from '../models/FeatureUIData' | ||
import { CanvasCoursesSectionNameValidator, ICanvasSectionNameInvalidError } from '../utils/canvasSectionNameValidator' | ||
import { CODE_NUMPAD_ENTER, CODE_RETURN } from 'keycode-js' | ||
|
||
const useStyles = makeStyles((theme) => ({ | ||
root: { | ||
backgroundColor: '#FAFAFA', | ||
height: 200 | ||
}, | ||
input: { | ||
width: '100%' | ||
}, | ||
button: { | ||
width: '100%' | ||
} | ||
})) | ||
|
||
export interface CreateSectionWidgetProps extends CCMComponentProps { | ||
onSectionCreated: (newSection: CanvasCourseSection) => void | ||
} | ||
|
||
function CreateSectionWidget (props: CreateSectionWidgetProps): JSX.Element { | ||
const classes = useStyles() | ||
const { enqueueSnackbar } = useSnackbar() | ||
const [newSectionName, setNewSectionName] = useState<string>('') | ||
const [isCreating, setIsCreating] = useState(false) | ||
const nameValidator = new CanvasCoursesSectionNameValidator(props.globals.course) | ||
|
||
const newSectionNameChanged = (event: ChangeEvent<HTMLInputElement>): void => { | ||
setNewSectionName(event.target.value) | ||
} | ||
|
||
const errorAlert = (errors: ICanvasSectionNameInvalidError[]): void => { | ||
const errorMessage = errors.map(e => { return e.reason }).join('<br/>') | ||
enqueueSnackbar(errorMessage, { | ||
variant: 'error' | ||
}) | ||
} | ||
|
||
const createSection = (): void => { | ||
if (newSectionName.trim().length === 0) { | ||
return | ||
} | ||
setIsCreating(true) | ||
nameValidator.validateSectionName(newSectionName).then(errors => { | ||
if (errors.length === 0) { | ||
addCourseSections(props.globals.course.id, [newSectionName]) | ||
.then(newSections => { | ||
props.onSectionCreated(newSections[0]) | ||
setNewSectionName('') | ||
}).catch(() => { | ||
enqueueSnackbar('Error adding section', { | ||
variant: 'error' | ||
}) | ||
}) | ||
} else { | ||
errorAlert(errors) | ||
} | ||
}).catch(() => { | ||
enqueueSnackbar('Error validating section name', { | ||
variant: 'error' | ||
}) | ||
}).finally(() => { | ||
setIsCreating(false) | ||
}) | ||
} | ||
|
||
const isCreateDisabled = (): boolean => { | ||
return isCreating || newSectionName.trim().length === 0 | ||
} | ||
|
||
const keyDown = (code: string): void => { | ||
if (isCreateDisabled()) return | ||
if (code === CODE_RETURN || code === CODE_NUMPAD_ENTER) { | ||
createSection() | ||
} | ||
} | ||
|
||
return ( | ||
<> | ||
<Grid container spacing={2}> | ||
<Grid item xs={12} sm={9}> | ||
<TextField className={classes.input} size='small' label='input the name of the new section' variant='outlined' id="outlined-basic" onChange={newSectionNameChanged} value={newSectionName} onKeyDown={(e) => keyDown(e.code)}/> | ||
</Grid> | ||
<Grid item xs={12} sm> | ||
<Button className={classes.button} variant="contained" color="primary" onClick={createSection} value={newSectionName} disabled={isCreateDisabled()}> | ||
Create | ||
</Button> | ||
chrisrrowland marked this conversation as resolved.
Show resolved
Hide resolved
|
||
</Grid> | ||
</Grid> | ||
</> | ||
) | ||
} | ||
|
||
export default CreateSectionWidget |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { Grid, List, ListItem, ListItemText, makeStyles, TextField } from '@material-ui/core' | ||
import ClearIcon from '@material-ui/icons/Clear' | ||
import React, { useEffect, useState } from 'react' | ||
import { CanvasCourseSection } from '../models/canvas' | ||
|
||
const useStyles = makeStyles((theme) => ({ | ||
listContainer: { | ||
overflow: 'auto', | ||
marginBottom: '5px' | ||
}, | ||
searchContainer: { | ||
textAlign: 'left' | ||
}, | ||
searchTextField: { | ||
width: '100%' | ||
} | ||
})) | ||
|
||
interface ISectionSelectorWidgetProps { | ||
sections: CanvasCourseSection[] | ||
selectedSections: CanvasCourseSection[] | ||
height: number | ||
multiSelect: boolean | ||
selectionUpdated: (section: CanvasCourseSection[]) => void | ||
} | ||
|
||
function SectionSelectorWidget (props: ISectionSelectorWidgetProps): JSX.Element { | ||
const classes = useStyles() | ||
|
||
const [sectionFilterText, setSectionFilterText] = useState<string>('') | ||
const [filteredSections, setFilteredSections] = useState<CanvasCourseSection[]>(props.sections) | ||
|
||
useEffect(() => { | ||
if (sectionFilterText.length === 0) { | ||
setFilteredSections(props.sections) | ||
} else { | ||
setFilteredSections(props.sections.filter(p => { return p.name.toUpperCase().includes(sectionFilterText.toUpperCase()) })) | ||
} | ||
}, [sectionFilterText, props.sections]) | ||
|
||
const handleListItemClick = ( | ||
sectionId: number | ||
): void => { | ||
let newSelections = [...props.selectedSections] | ||
const alreadySelected = props.selectedSections.filter(s => { return s.id === sectionId }) | ||
if (alreadySelected.length > 0) { | ||
newSelections.splice(newSelections.indexOf(alreadySelected[0]), 1) | ||
} else { | ||
if (props.multiSelect) { | ||
newSelections.push(filteredSections.filter(s => { return s.id === sectionId })[0]) | ||
} else { | ||
newSelections = [filteredSections.filter(s => { return s.id === sectionId })[0]] | ||
} | ||
} | ||
props.selectionUpdated(newSelections) | ||
} | ||
|
||
const isSectionSelected = (sectionId: number): boolean => { | ||
return props.selectedSections.map(s => { return s.id }).includes(sectionId) | ||
} | ||
|
||
const searchChange = (event: React.ChangeEvent<HTMLInputElement>): void => { | ||
setSectionFilterText(event.target.value) | ||
} | ||
|
||
const clearSearch = (): void => { | ||
setSectionFilterText('') | ||
} | ||
|
||
const getSearchTextFieldEndAdornment = (hasText: boolean): JSX.Element => { | ||
if (!hasText) { | ||
return (<></>) | ||
} else { | ||
return (<ClearIcon onClick={clearSearch}/>) | ||
} | ||
} | ||
// Passing in the height in the props seems like the wrong solution, but wanted to move on from solving that for now | ||
return ( | ||
<> | ||
<Grid container> | ||
<Grid item container className={classes.searchContainer} xs={12}> | ||
<TextField className={classes.searchTextField} onChange={searchChange} value={sectionFilterText} id='textField_Search' size='small' label='Search Sections' variant='outlined' InputProps={{ endAdornment: getSearchTextFieldEndAdornment(sectionFilterText.length > 0) }}/> | ||
</Grid> | ||
<Grid item xs={12}> | ||
<List className={classes.listContainer} style={{ maxHeight: props.height }}> | ||
{filteredSections.map((section, index) => { | ||
return (<ListItem divider key={section.id} button selected={isSectionSelected(section.id)} onClick={(event) => handleListItemClick(section.id)}> | ||
<ListItemText primary={section.name} secondary={`${section.total_students ?? '?'} students`}></ListItemText> | ||
</ListItem>) | ||
})} | ||
</List> | ||
</Grid> | ||
</Grid> | ||
</> | ||
) | ||
} | ||
|
||
export default SectionSelectorWidget |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The swagger link is looking better now. Thanks for the fix!