fix temporary enrollment modal data reset bug
This fixes a bug where the modal data was not being reset when the modal “start over” button was clicked and/or modal was closed. This caused the modal to display to not update the data when restarting the temporary enrollment process. closes FOO-3967 flag=temporary_enrollments test plan: - enable temporary_enrollments feature flag on root account - enable permissions: User - Temporary Enrollments - create three users on account (user1/user2/user3) - create course add user1 as teacher - add module/assignment and publish everything - go to course/people page - click the temporary enroll icon for user1 (the provider) - search for user2 (recipient) - expect user2 to be approved for temporary enrollments - cancel/close modal and/or start over - search for user3 (recipient) - expect user3 to be approved for temporary enrollments - prior to this bug being fixed, user2 would show here instead of user3; so, expect user2 to not show at this step Change-Id: I428456bffa984e866dc9d558b9f92abef305ae8c Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/330886 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> QA-Review: Michael Hulse <michael.hulse@instructure.com> Product-Review: Michael Hulse <michael.hulse@instructure.com> Reviewed-by: August Thornton <august@instructure.com>
This commit is contained in:
parent
58b6bb2d38
commit
2890a936e7
|
@ -22,28 +22,28 @@ import {Spinner} from '@instructure/ui-spinner'
|
|||
import {Course, Enrollment, Role, Section} from './types'
|
||||
|
||||
interface RoleChoice {
|
||||
id: string
|
||||
name: string
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
enrollmentsByCourse: Course[] | any
|
||||
roles: Role[] | any
|
||||
selectedRole: RoleChoice
|
||||
createEnroll?: Function
|
||||
readonly enrollmentsByCourse: Course[] | any
|
||||
readonly roles: Role[] | any
|
||||
readonly selectedRole: RoleChoice
|
||||
readonly createEnroll?: Function
|
||||
}
|
||||
|
||||
export interface NodeStructure {
|
||||
children: NodeStructure[]
|
||||
enrollId?: string
|
||||
id: string
|
||||
readonly children: NodeStructure[]
|
||||
readonly enrollId?: string
|
||||
readonly id: string
|
||||
isCheck: boolean
|
||||
isMismatch?: boolean
|
||||
isMixed: boolean
|
||||
isToggle?: boolean
|
||||
label: string
|
||||
parent?: NodeStructure
|
||||
workState?: string
|
||||
readonly label: string
|
||||
readonly parent?: NodeStructure
|
||||
readonly workState?: string
|
||||
}
|
||||
|
||||
export function EnrollmentTree(props: Props) {
|
||||
|
|
|
@ -56,7 +56,7 @@ const I18n = useI18nScope('temporary_enrollment')
|
|||
// initialize analytics props
|
||||
const analyticProps = createAnalyticPropsGenerator(MODULE_NAME)
|
||||
|
||||
interface Role {
|
||||
interface EnrollmentRole {
|
||||
readonly id: string
|
||||
readonly base_role_name: string
|
||||
}
|
||||
|
@ -144,15 +144,15 @@ function getStoredData(): StoredData {
|
|||
}
|
||||
}
|
||||
|
||||
interface EnrollmentPropsFunctionProps {
|
||||
contextType: EnrollmentType
|
||||
enrollment: User
|
||||
user: User
|
||||
interface EnrollmentAndUserProps {
|
||||
readonly enrollmentProps: User
|
||||
readonly userProps: User
|
||||
}
|
||||
|
||||
interface EnrollmentAndUserProps {
|
||||
enrollmentProps: User
|
||||
userProps: User
|
||||
interface EnrollmentAndUserContextProps {
|
||||
readonly contextType: EnrollmentType
|
||||
readonly enrollment: User
|
||||
readonly user: User
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -166,7 +166,7 @@ interface EnrollmentAndUserProps {
|
|||
* @returns {Object} Enrollment and user props
|
||||
*/
|
||||
export function getEnrollmentAndUserProps(
|
||||
props: EnrollmentPropsFunctionProps
|
||||
props: EnrollmentAndUserContextProps
|
||||
): EnrollmentAndUserProps {
|
||||
const {contextType, enrollment, user} = props
|
||||
const enrollmentProps = contextType === RECIPIENT ? user : enrollment
|
||||
|
@ -268,7 +268,9 @@ export function TempEnrollAssign(props: Props) {
|
|||
}
|
||||
|
||||
const handleRoleSearchChange = (event: ChangeEvent, selectedOption: {id: string}) => {
|
||||
const foundRole: Role | undefined = props.roles.find(role => role.id === selectedOption.id)
|
||||
const foundRole: EnrollmentRole | undefined = props.roles.find(
|
||||
role => role.id === selectedOption.id
|
||||
)
|
||||
const name = foundRole ? removeStringAffix(foundRole.base_role_name, 'Enrollment') : ''
|
||||
|
||||
setRoleChoice({
|
||||
|
|
|
@ -32,7 +32,7 @@ import {TempEnrollSearch} from './TempEnrollSearch'
|
|||
import {TempEnrollEdit} from './TempEnrollEdit'
|
||||
import {TempEnrollAssign} from './TempEnrollAssign'
|
||||
import {Flex} from '@instructure/ui-flex'
|
||||
import {Enrollment, EnrollmentType, MODULE_NAME, TempEnrollPermissions, User} from './types'
|
||||
import {Enrollment, EnrollmentType, MODULE_NAME, Role, TempEnrollPermissions, User} from './types'
|
||||
import {showFlashSuccess} from '@canvas/alerts/react/FlashAlert'
|
||||
import {createAnalyticPropsGenerator} from './util/analytics'
|
||||
|
||||
|
@ -46,20 +46,20 @@ interface Props {
|
|||
readonly enrollmentType: EnrollmentType
|
||||
readonly children: ReactElement
|
||||
readonly user: {
|
||||
id: string
|
||||
name: string
|
||||
avatar_url?: string
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly avatar_url?: string
|
||||
}
|
||||
readonly canReadSIS?: boolean
|
||||
readonly permissions: {
|
||||
teacher: boolean
|
||||
ta: boolean
|
||||
student: boolean
|
||||
observer: boolean
|
||||
designer: boolean
|
||||
readonly teacher: boolean
|
||||
readonly ta: boolean
|
||||
readonly student: boolean
|
||||
readonly observer: boolean
|
||||
readonly designer: boolean
|
||||
}
|
||||
readonly accountId: string
|
||||
readonly roles: {id: string; label: string; base_role_name: string}[]
|
||||
readonly roles: Role[]
|
||||
readonly onOpen?: () => void
|
||||
readonly onClose?: () => void
|
||||
readonly defaultOpen?: boolean
|
||||
|
@ -90,8 +90,11 @@ export function TempEnrollModal(props: Props) {
|
|||
}
|
||||
}, [props.tempEnrollments])
|
||||
|
||||
function resetState(pg: number = 0) {
|
||||
setPage(pg)
|
||||
const resetState = () => {
|
||||
setPage(0)
|
||||
setEnrollment(null)
|
||||
setIsViewingAssignFromEdit(false)
|
||||
setEnrollmentData([])
|
||||
|
||||
if (props.isEditMode && props.onToggleEditMode) {
|
||||
props.onToggleEditMode(false)
|
||||
|
@ -154,7 +157,7 @@ export function TempEnrollModal(props: Props) {
|
|||
|
||||
const handleResetToBeginning = () => {
|
||||
resetState()
|
||||
setPage((p: number) => p - 1)
|
||||
setPage(0)
|
||||
}
|
||||
|
||||
const handlePageTransition = () => {
|
||||
|
@ -172,7 +175,8 @@ export function TempEnrollModal(props: Props) {
|
|||
|
||||
const handleGoToAssignPageWithEnrollment = (chosenEnrollment: any) => {
|
||||
setEnrollment(chosenEnrollment)
|
||||
resetState(2)
|
||||
setPage(2)
|
||||
resetState()
|
||||
setIsViewingAssignFromEdit(true)
|
||||
}
|
||||
|
||||
|
@ -231,7 +235,7 @@ export function TempEnrollModal(props: Props) {
|
|||
page={page}
|
||||
searchFail={handleSearchFailure}
|
||||
searchSuccess={handleSetEnrollmentFromSearch}
|
||||
foundEnroll={enrollment !== null ? enrollment : undefined}
|
||||
foundEnroll={enrollment}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ interface Props {
|
|||
readonly searchSuccess: Function
|
||||
readonly canReadSIS?: boolean
|
||||
readonly accountId: string
|
||||
readonly foundEnroll?: User
|
||||
readonly foundEnroll?: User | null
|
||||
}
|
||||
|
||||
export function TempEnrollSearch(props: Props) {
|
||||
|
@ -135,6 +135,7 @@ export function TempEnrollSearch(props: Props) {
|
|||
useEffect(() => {
|
||||
if (props.page === 1 && !props.foundEnroll) {
|
||||
setLoading(true)
|
||||
|
||||
const findUser = async () => {
|
||||
try {
|
||||
const {json} = await doFetchApi({
|
||||
|
@ -142,14 +143,18 @@ export function TempEnrollSearch(props: Props) {
|
|||
method: 'POST',
|
||||
params: {user_list: search, v2: true, search_type: searchType},
|
||||
})
|
||||
|
||||
processSearchApiResponse(json)
|
||||
} catch (error: any) {
|
||||
setMessage(error.message)
|
||||
setEnrollment(EMPTY_USER)
|
||||
|
||||
props.searchFail()
|
||||
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
findUser()
|
||||
} else if (props.foundEnroll) {
|
||||
setEnrollment({...props.foundEnroll})
|
||||
|
|
|
@ -24,36 +24,37 @@ import {NodeStructure} from '../EnrollmentTree'
|
|||
const checkCallback = jest.fn()
|
||||
const toggleCallback = jest.fn()
|
||||
|
||||
interface TestableNodeStructure extends NodeStructure {
|
||||
parent?: NodeStructure
|
||||
}
|
||||
|
||||
const emptyNode = {
|
||||
id: '',
|
||||
label: '',
|
||||
// eslint-disable-next-line no-array-constructor
|
||||
children: new Array<NodeStructure>(),
|
||||
children: [],
|
||||
isMixed: false,
|
||||
isCheck: false,
|
||||
}
|
||||
|
||||
const section2Node: NodeStructure = {
|
||||
const section2Node: TestableNodeStructure = {
|
||||
id: 's2',
|
||||
label: 'Section 2',
|
||||
// eslint-disable-next-line no-array-constructor
|
||||
children: new Array<NodeStructure>(),
|
||||
children: [],
|
||||
parent: emptyNode,
|
||||
isCheck: false,
|
||||
isMixed: false,
|
||||
}
|
||||
|
||||
const section1Node: NodeStructure = {
|
||||
const section1Node: TestableNodeStructure = {
|
||||
id: 's1',
|
||||
label: 'Section 1',
|
||||
// eslint-disable-next-line no-array-constructor
|
||||
children: new Array<NodeStructure>(),
|
||||
children: [],
|
||||
parent: emptyNode,
|
||||
isCheck: false,
|
||||
isMixed: false,
|
||||
}
|
||||
|
||||
const courseNode: NodeStructure = {
|
||||
const courseNode: TestableNodeStructure = {
|
||||
id: 'c1',
|
||||
label: 'Course 1',
|
||||
children: [section1Node],
|
||||
|
@ -63,7 +64,7 @@ const courseNode: NodeStructure = {
|
|||
isMixed: false,
|
||||
}
|
||||
|
||||
const roleNode: NodeStructure = {
|
||||
const roleNode: TestableNodeStructure = {
|
||||
enrollId: '1',
|
||||
id: 'r1',
|
||||
label: 'Role 1',
|
||||
|
|
|
@ -59,6 +59,7 @@ const modalProps = {
|
|||
{
|
||||
base_role_name: 'TeacherEnrollment',
|
||||
id: '234',
|
||||
role: 'TeacherEnrollment',
|
||||
label: 'Teacher',
|
||||
},
|
||||
],
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {fetchTemporaryEnrollments, deleteEnrollment} from '../../api/enrollment'
|
||||
import {deleteEnrollment, fetchTemporaryEnrollments} from '../../api/enrollment'
|
||||
import doFetchApi from '@canvas/do-fetch-api-effect'
|
||||
|
||||
// Mock the API call
|
||||
|
@ -116,6 +116,22 @@ describe('enrollment api', () => {
|
|||
|
||||
expect(mockConsoleError).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.skip('handles deletion without onDelete gracefully', async () => {
|
||||
;(doFetchApi as jest.Mock).mockResolvedValue({response: {status: 200}})
|
||||
|
||||
await expect(deleteEnrollment(1, 2)).resolves.not.toThrow()
|
||||
})
|
||||
|
||||
it.skip('handles non-200 status code gracefully', async () => {
|
||||
;(doFetchApi as jest.Mock).mockResolvedValue({response: {status: 404}})
|
||||
|
||||
try {
|
||||
await deleteEnrollment(1, 2)
|
||||
} catch (e: any) {
|
||||
expect(e.message).toBe('Failed to delete enrollment: HTTP status code 404')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue