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

Create available_participant_types endpoint #45579

Merged
merged 2 commits into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 25 additions & 2 deletions apps/src/templates/teacherDashboard/teacherSectionsRedux.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const USER_EDITABLE_SECTION_PROPS = [
'lessonExtras',
'pairingAllowed',
'ttsAutoplayEnabled',
'participantType',
'courseId',
'scriptId',
'courseOfferingId',
Expand Down Expand Up @@ -48,6 +49,8 @@ const importUrlByProvider = {
// Action keys
//
const SET_COURSE_OFFERINGS = 'teacherDashboard/SET_COURSE_OFFERINGS';
const SET_AVAILABLE_PARTICIPANT_TYPES =
'teacherDashboard/SET_AVAILABLE_PARTICIPANT_TYPES';
const SET_VALID_ASSIGNMENTS = 'teacherDashboard/SET_VALID_ASSIGNMENTS';
const SET_STUDENT_SECTION = 'teacherDashboard/SET_STUDENT_SECTION';
const SET_PAGE_TYPE = 'teacherDashboard/SET_PAGE_TYPE';
Expand Down Expand Up @@ -128,6 +131,10 @@ export const setCourseOfferings = courseOfferings => ({
type: SET_COURSE_OFFERINGS,
courseOfferings
});
export const setAvailableParticipantTypes = availableParticipantTypes => ({
type: SET_AVAILABLE_PARTICIPANT_TYPES,
availableParticipantTypes
});
export const setStudentsForCurrentSection = (sectionId, studentInfo) => ({
type: SET_STUDENT_SECTION,
sectionId: sectionId,
Expand Down Expand Up @@ -388,7 +395,8 @@ export const asyncLoadSectionData = id => dispatch => {
'/dashboardapi/sections',
`/dashboardapi/courses`,
'/dashboardapi/sections/valid_scripts',
'/dashboardapi/sections/valid_course_offerings'
'/dashboardapi/sections/valid_course_offerings',
'/dashboardapi/sections/available_participant_types'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice job finding a way to make this request in parallel with the other requests 👍

];
if (id) {
apis.push('/dashboardapi/sections/' + id + '/students');
Expand All @@ -401,10 +409,16 @@ export const asyncLoadSectionData = id => dispatch => {
validCourses,
validScripts,
validCourseOfferings,
availableParticipantTypes,
students
]) => {
dispatch(setValidAssignments(validCourses, validScripts));
dispatch(setCourseOfferings(validCourseOfferings));
dispatch(
setAvailableParticipantTypes(
availableParticipantTypes.availableParticipantTypes
)
);
dispatch(setSections(sections));
if (id) {
dispatch(setStudentsForCurrentSection(id, students));
Expand Down Expand Up @@ -542,6 +556,8 @@ const initialState = {
// with options like "CSD", "Course A", or "Frozen". See the
// assignmentCourseOfferingShape PropType.
courseOfferings: {},
// The participant types the user can create sections for
availableParticipantTypes: [],
// Mapping from sectionId to section object
sections: {},
// List of students in section currently being edited (see studentShape PropType)
Expand Down Expand Up @@ -569,13 +585,13 @@ const initialState = {
// DCDO Flag - show/hide Lock Section field
showLockSectionField: null
};

/**
* Generate shape for new section
* @param id
* @param loginType
* @returns {sectionShape}
*/

function newSectionData(id, loginType) {
return {
id: id,
Expand Down Expand Up @@ -716,6 +732,13 @@ export default function teacherSections(state = initialState, action) {
};
}

if (action.type === SET_AVAILABLE_PARTICIPANT_TYPES) {
return {
...state,
availableParticipantTypes: action.availableParticipantTypes
};
}

if (action.type === SET_STUDENT_SECTION) {
const students = action.students || [];
const selectedStudents = students.map(student =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1113,7 +1113,7 @@ describe('teacherSectionsRedux', () => {
it('sets asyncLoadComplete to true after success responses', () => {
const promise = store.dispatch(asyncLoadSectionData('id'));

expect(server.requests).to.have.length(5);
expect(server.requests).to.have.length(6);
server.respondWith('GET', '/dashboardapi/sections', successResponse());
server.respondWith('GET', '/dashboardapi/courses', successResponse());
server.respondWith(
Expand All @@ -1126,6 +1126,11 @@ describe('teacherSectionsRedux', () => {
'/dashboardapi/sections/valid_course_offerings',
successResponse()
);
server.respondWith(
'GET',
'/dashboardapi/sections/available_participant_types',
successResponse()
);
server.respondWith(
'GET',
'/dashboardapi/sections/id/students',
Expand Down Expand Up @@ -1159,7 +1164,7 @@ describe('teacherSectionsRedux', () => {
const promise = store.dispatch(asyncLoadSectionData());
expect(state().sections).to.deep.equal({});

expect(server.requests).to.have.length(4);
expect(server.requests).to.have.length(5);
server.respondWith(
'GET',
'/dashboardapi/sections',
Expand All @@ -1176,6 +1181,11 @@ describe('teacherSectionsRedux', () => {
'/dashboardapi/sections/valid_course_offerings',
successResponse()
);
server.respondWith(
'GET',
'/dashboardapi/sections/available_participant_types',
successResponse({availableParticipantTypes: ['student']})
);
server.respond();

return promise.then(() => {
Expand All @@ -1187,7 +1197,7 @@ describe('teacherSectionsRedux', () => {
const promise = store.dispatch(asyncLoadSectionData());
expect(state().courseOfferings).to.deep.equal({});

expect(server.requests).to.have.length(4);
expect(server.requests).to.have.length(5);
server.respondWith('GET', '/dashboardapi/sections', successResponse());
server.respondWith(
'GET',
Expand All @@ -1204,6 +1214,11 @@ describe('teacherSectionsRedux', () => {
'/dashboardapi/sections/valid_course_offerings',
successResponse(courseOfferings)
);
server.respondWith(
'GET',
'/dashboardapi/sections/available_participant_types',
successResponse({availableParticipantTypes: ['student']})
);
server.respond();

return promise.then(() => {
Expand All @@ -1213,11 +1228,44 @@ describe('teacherSectionsRedux', () => {
});
});

it('sets availableParticipantTypes from server responses', () => {
const promise = store.dispatch(asyncLoadSectionData());
expect(state().courseOfferings).to.deep.equal({});

expect(server.requests).to.have.length(5);
server.respondWith('GET', '/dashboardapi/sections', successResponse());
server.respondWith(
'GET',
'/dashboardapi/courses',
successResponse(validCourses)
);
server.respondWith(
'GET',
'/dashboardapi/sections/valid_scripts',
successResponse(validScripts)
);
server.respondWith(
'GET',
'/dashboardapi/sections/valid_course_offerings',
successResponse(courseOfferings)
);
server.respondWith(
'GET',
'/dashboardapi/sections/available_participant_types',
successResponse({availableParticipantTypes: ['student', 'teacher']})
);
server.respond();

return promise.then(() => {
expect(state().availableParticipantTypes).to.have.length(2);
});
});

it('sets validAssignments from server responses', () => {
const promise = store.dispatch(asyncLoadSectionData());
expect(state().validAssignments).to.deep.equal({});

expect(server.requests).to.have.length(4);
expect(server.requests).to.have.length(5);
server.respondWith('GET', '/dashboardapi/sections', successResponse());
server.respondWith(
'GET',
Expand All @@ -1234,6 +1282,11 @@ describe('teacherSectionsRedux', () => {
'/dashboardapi/sections/valid_course_offerings',
successResponse(courseOfferings)
);
server.respondWith(
'GET',
'/dashboardapi/sections/available_participant_types',
successResponse({availableParticipantTypes: ['student', 'teacher']})
);
server.respond();

return promise.then(() => {
Expand All @@ -1247,7 +1300,7 @@ describe('teacherSectionsRedux', () => {
const promise = store.dispatch(asyncLoadSectionData('id'));
expect(state().validAssignments).to.deep.equal({});

expect(server.requests).to.have.length(5);
expect(server.requests).to.have.length(6);
server.respondWith('GET', '/dashboardapi/sections', successResponse());
server.respondWith('GET', '/dashboardapi/courses', successResponse());
server.respondWith(
Expand All @@ -1260,6 +1313,11 @@ describe('teacherSectionsRedux', () => {
'/dashboardapi/sections/valid_course_offerings',
successResponse(courseOfferings)
);
server.respondWith(
'GET',
'/dashboardapi/sections/available_participant_types',
successResponse({availableParticipantTypes: ['student']})
);
server.respondWith(
'GET',
'/dashboardapi/sections/id/students',
Expand Down Expand Up @@ -1687,6 +1745,11 @@ describe('teacherSectionsRedux', () => {
'/dashboardapi/sections/valid_course_offerings',
successResponse([])
);
server.respondWith(
'GET',
'/dashboardapi/sections/available_participant_types',
successResponse({availableParticipantTypes: ['student']})
);
});
afterEach(() => server.restore());

Expand Down Expand Up @@ -1778,7 +1841,7 @@ describe('teacherSectionsRedux', () => {
importOrUpdateRoster(TEST_COURSE_ID, TEST_COURSE_NAME)
);
return expect(promise).to.be.fulfilled.then(() => {
expect(server.requests).to.have.length(5);
expect(server.requests).to.have.length(6);
expect(server.requests[1].method).to.equal('GET');
expect(server.requests[1].url).to.equal('/dashboardapi/sections');
expect(server.requests[2].method).to.equal('GET');
Expand All @@ -1791,6 +1854,10 @@ describe('teacherSectionsRedux', () => {
expect(server.requests[4].url).to.equal(
'/dashboardapi/sections/valid_course_offerings'
);
expect(server.requests[5].method).to.equal('GET');
expect(server.requests[5].url).to.equal(
'/dashboardapi/sections/available_participant_types'
);
expect(Object.keys(getState().teacherSections.sections)).to.have.length(
sections.length
);
Expand Down
16 changes: 16 additions & 0 deletions dashboard/app/controllers/api/v1/sections_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,22 @@ def valid_course_offerings
render json: course_offerings
end

# GET /api/v1/sections/available_participant_types
def available_participant_types
return head :forbidden unless current_user && !current_user.student?

participant_types =
if current_user.permission?(UserPermission::PLC_REVIEWER) || current_user.permission?(UserPermission::UNIVERSAL_INSTRUCTOR) || current_user.permission?(UserPermission::LEVELBUILDER)
[SharedCourseConstants::PARTICIPANT_AUDIENCE.student, SharedCourseConstants::PARTICIPANT_AUDIENCE.teacher, SharedCourseConstants::PARTICIPANT_AUDIENCE.facilitator]
elsif current_user.permission?(UserPermission::FACILITATOR)
[SharedCourseConstants::PARTICIPANT_AUDIENCE.student, SharedCourseConstants::PARTICIPANT_AUDIENCE.teacher]
else
[SharedCourseConstants::PARTICIPANT_AUDIENCE.student]
end

render json: {availableParticipantTypes: participant_types}
end

# GET /api/v1/sections/require_captcha
# Get the recaptcha site key for frontend and whether current user requires captcha verification
def require_captcha
Expand Down
1 change: 1 addition & 0 deletions dashboard/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
get 'membership'
get 'valid_scripts'
get 'valid_course_offerings'
get 'available_participant_types'
get 'require_captcha'
end
end
Expand Down
50 changes: 50 additions & 0 deletions dashboard/test/controllers/api/v1/sections_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ class Api::V1::SectionsControllerTest < ActionController::TestCase
self.use_transactional_test_case = true

setup_all do
@levelbuilder = create(:levelbuilder)
@universal_instructor = create(:universal_instructor)
@plc_reviewer = create(:plc_reviewer)
@facilitator = create(:facilitator)
@teacher = create(:teacher)
@section = create(:section, user: @teacher, login_type: 'word')
@student = create(:follower, section: @section).student_user
Expand Down Expand Up @@ -1019,6 +1023,52 @@ def returned_section
assert_response :forbidden
end

test "available_participant_types: returns forbidden if no user" do
get :available_participant_types
assert_response :forbidden
end

test "available_participant_types: returns forbidden if student" do
sign_in @student_with_script
get :available_participant_types
assert_response :forbidden
end

test "available_participant_types: returns just students if teacher" do
sign_in @teacher
get :available_participant_types
assert_response :success
assert_equal(['student'], json_response["availableParticipantTypes"])
end

test "available_participant_types: returns students and teachers if facilitator" do
sign_in @facilitator
get :available_participant_types
assert_response :success
assert_equal(['student', 'teacher'], json_response["availableParticipantTypes"])
end

test "available_participant_types: returns all 3 options if universal instructor" do
sign_in @universal_instructor
get :available_participant_types
assert_response :success
assert_equal(['student', 'teacher', 'facilitator'], json_response["availableParticipantTypes"])
end

test "available_participant_types: returns all 3 options if plc reviewer" do
sign_in @plc_reviewer
get :available_participant_types
assert_response :success
assert_equal(['student', 'teacher', 'facilitator'], json_response["availableParticipantTypes"])
end

test "available_participant_types: returns all 3 options if levelbuilder" do
sign_in @levelbuilder
get :available_participant_types
assert_response :success
assert_equal(['student', 'teacher', 'facilitator'], json_response["availableParticipantTypes"])
end

test "membership: returns status 403 'Forbidden' when not signed in" do
get :membership
assert_response :forbidden
Expand Down