Skip to content

Commit

Permalink
Merge pull request #26562 from code-dot-org/manage-students-react-das…
Browse files Browse the repository at this point in the history
…hboard

Render ManageStudentTable on studio Teacher Dashboard
  • Loading branch information
Madelyn Kasula committed Jan 28, 2019
2 parents dee34e6 + baf402a commit b150aca
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 162 deletions.
86 changes: 13 additions & 73 deletions apps/src/redux/sectionDataRedux.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,30 @@
import { PropTypes } from 'react';
import $ from 'jquery';

/**
* Reducer for section data in teacher dashboard.
* Tab specific reducers can import actions from this file
* if they need to respond to a section changing.
*/

/**
* Shape for the section
* The section we get directly from angular right now. This gives us a
* different shape than some other places we use sections. For now, I'm just
* going to document the parts of section that we use here
*/
export const sectionDataPropType = PropTypes.shape({
/**
* Shape for the section
* The section we get directly from angular right now. This gives us a
* different shape than some other places we use sections. For now, I'm just
* going to document the parts of section that we use here
*/
export const sectionDataPropType = PropTypes.shape({
id: PropTypes.number.isRequired,
script: PropTypes.object,
students: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
script: PropTypes.object,
students: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
})).isRequired
});

/**
* Status for loading section data from server.
*/
export const LoadingStatus = {
IN_PROGRESS: "inProgress",
SUCCESS: "success",
FAIL: "fail",
};
name: PropTypes.string.isRequired,
})).isRequired
});

/**
* Action type constants
*/
export const SET_SECTION = 'sectionData/SET_SECTION';
export const CLEAR_SECTION_DATA = 'sectionData/CLEAR_SECTION_DATA';
export const START_LOADING_SECTION = 'sectionData/START_LOADING_SECTION';
export const LOAD_SECTION_SUCCESS = 'sectionData/LOAD_SECTION_SUCCESS';
export const LOAD_SECTION_FAIL = 'sectionData/LOAD_SECTION_FAIL';

/**
* Action creators
Expand All @@ -61,7 +47,6 @@ export const setSection = (section) => {
*/
const initialState = {
section: {},
loadingStatus: null,
};

/**
Expand All @@ -78,31 +63,6 @@ export default function sectionData(state=initialState, action) {
};
}

if (action.type === CLEAR_SECTION_DATA) {
return initialState;
}

if (action.type === START_LOADING_SECTION) {
return {
...state,
loadingStatus: LoadingStatus.IN_PROGRESS,
};
}

if (action.type === LOAD_SECTION_SUCCESS) {
return {
...state,
loadingStatus: LoadingStatus.SUCCESS,
};
}

if (action.type === LOAD_SECTION_FAIL) {
return {
...state,
loadingStatus: LoadingStatus.FAIL,
};
}

return state;
}

Expand All @@ -116,23 +76,3 @@ export const getTotalStudentCount = (state) => {
export const getStudentList = (state) => {
return state.sectionData.section.students;
};

/**
* Helper functions
*/
export const asyncSetSection = (sectionId) => (dispatch) => {
// Clear any existing section data and begin loading.
dispatch({ type: CLEAR_SECTION_DATA });
dispatch({ type: START_LOADING_SECTION });

$.ajax({
url: `/api/v1/sections/${sectionId}`,
method: 'GET',
dataType: 'json'
}).done(section => {
dispatch(setSection(section));
dispatch({ type: LOAD_SECTION_SUCCESS });
}).fail((jqXhr, status) => {
dispatch({ type: LOAD_SECTION_FAIL });
});
};
45 changes: 36 additions & 9 deletions apps/src/sites/studio/pages/teacher_dashboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,58 @@ import ReactDOM from 'react-dom';
import {BrowserRouter as Router, Route} from 'react-router-dom';
import {Provider} from 'react-redux';
import {getStore, registerReducers} from '@cdo/apps/redux';
import sectionData, {asyncSetSection} from '@cdo/apps/redux/sectionDataRedux';
import manageStudents, {
setLoginType,
setStudents,
convertStudentServerData,
toggleSharingColumn,
} from '@cdo/apps/templates/manageStudents/manageStudentsRedux';
import teacherSections, {setSections, selectSection} from '@cdo/apps/templates/teacherDashboard/teacherSectionsRedux';
import sectionData, {setSection} from '@cdo/apps/redux/sectionDataRedux';
import stats, {asyncSetCompletedLevelCount} from '@cdo/apps/templates/teacherDashboard/statsRedux';
import TeacherDashboard from '@cdo/apps/templates/teacherDashboard/TeacherDashboard';

const script = document.querySelector('script[data-dashboard]');
const scriptData = JSON.parse(script.dataset.dashboard);
const sectionId = scriptData.section_id;
const baseUrl = `/teacher_dashboard/sections/${sectionId}`;
const section = scriptData.section;
const baseUrl = `/teacher_dashboard/sections/${section.id}`;

$(document).ready(function () {
registerReducers({sectionData, stats});
registerReducers({teacherSections, sectionData, manageStudents, stats});
const store = getStore();
store.dispatch(asyncSetSection(sectionId));
store.dispatch(asyncSetCompletedLevelCount(sectionId));
// TODO: (madelynkasula) remove duplication in sectionData.setSection and teacherSections.setSections
store.dispatch(setSection(section));
store.dispatch(setSections([section]));

store.dispatch(selectSection(section.id));
store.dispatch(setLoginType(section.login_type));
store.dispatch(asyncSetCompletedLevelCount(section.id));

// Show share column by default for CSD and CSP courses,
// or any script in either course.
const courseFamiliesToShowShareColumn = ["csd", "csp"];
if (courseFamiliesToShowShareColumn.includes(section.script.course_family_name)) {
store.dispatch(toggleSharingColumn());
}

$.ajax({
method: 'GET',
url: `/dashboardapi/sections/${section.id}/students`,
dataType: 'json'
}).done(studentData => {
const convertedStudentData = convertStudentServerData(studentData, section.login_type, section.id);
store.dispatch(setStudents(convertedStudentData));
});

ReactDOM.render(
<Provider store={store}>
<Router basename={baseUrl}>
<Route
path="/"
component={props => <TeacherDashboard {...props} sectionId={sectionId} studioUrlPrefix=""/>}
component={props => <TeacherDashboard {...props} sectionId={section.id} section={section} studioUrlPrefix=""/>}
/>
</Router>
</Provider>
,
</Provider>,
document.getElementById('teacher-dashboard')
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import SectionProjectsList from './SectionProjectsList';

class SectionProjectsListWithData extends Component {
static propTypes = {
sectionId: PropTypes.string,
sectionId: PropTypes.number,
studioUrlPrefix: PropTypes.string
};

Expand Down
25 changes: 5 additions & 20 deletions apps/src/templates/teacherDashboard/StatsTableWithData.jsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,23 @@
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import i18n from '@cdo/locale';
import Spinner from '@cdo/apps/code-studio/pd/components/spinner';
import StatsTable from './StatsTable';
import {LoadingStatus} from '@cdo/apps/redux/sectionDataRedux';
import {getStudentsCompletedLevelCount} from './statsRedux';

class StatsTableWithData extends Component {
static propTypes = {
// Props provided by redux.
section: PropTypes.object,
sectionLoadingStatus: PropTypes.string,
studentsCompletedLevelCount: PropTypes.object,
};

render() {
const {section, sectionLoadingStatus, studentsCompletedLevelCount} = this.props;
const {section, studentsCompletedLevelCount} = this.props;

return (
<div>
{(!sectionLoadingStatus || sectionLoadingStatus === LoadingStatus.IN_PROGRESS) &&
<Spinner/>
}
{sectionLoadingStatus === LoadingStatus.SUCCESS &&
<StatsTable
section={section}
studentsCompletedLevelCount={studentsCompletedLevelCount}
/>
}
{sectionLoadingStatus === LoadingStatus.FAIL &&
<div>{i18n.statsTableFailure()}</div>
}
</div>
<StatsTable
section={section}
studentsCompletedLevelCount={studentsCompletedLevelCount}
/>
);
}
}
Expand All @@ -40,6 +26,5 @@ export const UnconnectedStatsTableWithData = StatsTableWithData;

export default connect(state => ({
section: state.sectionData.section,
sectionLoadingStatus: state.sectionData.loadingStatus,
studentsCompletedLevelCount: getStudentsCompletedLevelCount(state, state.sectionData.section.id),
}))(StatsTableWithData);
12 changes: 8 additions & 4 deletions apps/src/templates/teacherDashboard/TeacherDashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import {Route, Switch} from 'react-router-dom';
import TeacherDashboardNavigation from './TeacherDashboardNavigation';
import StatsTableWithData from './StatsTableWithData';
import SectionProjectsListWithData from '@cdo/apps/templates/projects/SectionProjectsListWithData';
import ManageStudentsTable from '@cdo/apps/templates/manageStudents/ManageStudentsTable';
import {summarizedSectionShape} from '@cdo/apps/templates/teacherDashboard/shapes';

export default class TeacherDashboard extends Component {
static propTypes = {
sectionId: PropTypes.string,
section: summarizedSectionShape.isRequired,
studioUrlPrefix: PropTypes.string
};

render() {
const {sectionId, studioUrlPrefix} = this.props;
const {section, studioUrlPrefix} = this.props;

return (
<div>
Expand All @@ -27,11 +29,13 @@ export default class TeacherDashboard extends Component {
/>
<Route
path="/manage_students"
component={props => <div>Manage Students content goes here!</div>}
component={props => <ManageStudentsTable {...props} studioUrlPrefix={studioUrlPrefix}/>
}
/>
{/* TODO: (madelynkasula) refactor SectionProjectsListWithData to use section from redux */}
<Route
path="/projects"
component={props => <SectionProjectsListWithData {...props} sectionId={sectionId} studioUrlPrefix={studioUrlPrefix}/>}
component={props => <SectionProjectsListWithData {...props} sectionId={section.id} studioUrlPrefix={studioUrlPrefix}/>}
/>
<Route
path="/text_responses"
Expand Down
27 changes: 27 additions & 0 deletions apps/src/templates/teacherDashboard/shapes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,33 @@ export const sectionShape = PropTypes.shape({
providerManaged: PropTypes.bool.isRequired,
});

// Used on the Teacher Dashboard for components that
// require more specific information.
export const summarizedSectionShape = PropTypes.shape({
assignedTitle: PropTypes.string.isRequired,
code: PropTypes.string.isRequired,
course_id: PropTypes.number,
currentUnitTitle: PropTypes.string,
grade: PropTypes.string,
hidden: PropTypes.bool,
id: PropTypes.number.isRequired,
linkToAssigned: PropTypes.string,
linkToCurrentUnit: PropTypes.string,
linkToProgress: PropTypes.string,
linkToStudents: PropTypes.string,
login_type: PropTypes.string,
name: PropTypes.string,
numberOfStudents: PropTypes.number,
pairing_allowed: PropTypes.bool,
providerManaged: PropTypes.bool,
script: PropTypes.object,
sharing_disabled: PropTypes.bool,
stage_extras: PropTypes.bool,
studentCount: PropTypes.number,
students: PropTypes.array,
teacherName: PropTypes.string
});

// An assignment is a course or script that a user can be assigned to.
export const assignmentShape = PropTypes.shape({
id: PropTypes.number.isRequired,
Expand Down
48 changes: 1 addition & 47 deletions apps/test/unit/redux/sectionDataReduxTest.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import { assert } from '../../util/configuredChai';
import sectionData, {
setSection,
CLEAR_SECTION_DATA,
START_LOADING_SECTION,
LOAD_SECTION_SUCCESS,
LOAD_SECTION_FAIL,
LoadingStatus,
} from '@cdo/apps/redux/sectionDataRedux';
import sectionData, { setSection } from '@cdo/apps/redux/sectionDataRedux';

const fakeSectionData = {
id: 123,
Expand Down Expand Up @@ -54,43 +47,4 @@ describe('sectionDataRedux', () => {
assert.deepEqual(nextState.section, sortedFakeSectionData);
});
});

describe('clearSectionData', () => {
it('resets section data redux to initial state', () => {
const state = { section: sortedFakeSectionData };
const action = { type: CLEAR_SECTION_DATA };
const nextState = sectionData(state, action);
assert.deepEqual(nextState.section, {});
});
});

describe('startLoadingSection', () => {
it('sets loadingStatus to in progress', () => {
const state = { section: sortedFakeSectionData };
const action = { type: START_LOADING_SECTION };
const nextState = sectionData(state, action);
assert.equal(nextState.loadingStatus, LoadingStatus.IN_PROGRESS);
assert.deepEqual(nextState.section, sortedFakeSectionData);
});
});

describe('loadSectionSuccess', () => {
it('sets loadingStatus to in progress', () => {
const state = { section: sortedFakeSectionData };
const action = { type: LOAD_SECTION_SUCCESS };
const nextState = sectionData(state, action);
assert.equal(nextState.loadingStatus, LoadingStatus.SUCCESS);
assert.deepEqual(nextState.section, sortedFakeSectionData);
});
});

describe('loadSectionFail', () => {
it('sets loadingStatus to in progress', () => {
const state = { section: sortedFakeSectionData };
const action = { type: LOAD_SECTION_FAIL };
const nextState = sectionData(state, action);
assert.equal(nextState.loadingStatus, LoadingStatus.FAIL);
assert.deepEqual(nextState.section, sortedFakeSectionData);
});
});
});
3 changes: 2 additions & 1 deletion dashboard/app/controllers/teacher_dashboard_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class TeacherDashboardController < ApplicationController
def index
@section_id = params[:section_id]
@section = Section.find_by! id: params[:section_id]
@section = @section.summarize
end
end

0 comments on commit b150aca

Please sign in to comment.