Skip to content

Commit

Permalink
Merge pull request #45579 from code-dot-org/eptools/dani/available_pa…
Browse files Browse the repository at this point in the history
…rticipant_types

Create available_participant_types endpoint
  • Loading branch information
dmcavoy committed Mar 31, 2022
2 parents 42d98de + ef028ed commit 40fd631
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 8 deletions.
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'
];
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 @@ -106,6 +106,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

0 comments on commit 40fd631

Please sign in to comment.