From 969d150cd1d973ca4e9465ebae04b91e0d23b0be Mon Sep 17 00:00:00 2001 From: Morgan Busch Date: Wed, 21 Jun 2023 08:48:15 -0400 Subject: [PATCH 1/7] adding a new data pull that grabs courses --- .../obojobo-repository/server/routes/stats.js | 14 +++ .../shared/actions/dashboard-actions.js | 11 +- .../shared/actions/shared-api-methods.js | 9 +- .../components/course-score-data-dialog.jsx | 29 +++++ .../shared/components/dashboard-hoc.js | 2 + .../shared/components/dashboard.jsx | 19 +++ .../components/module-options-dialog.jsx | 12 ++ .../shared/components/stats/course-stats.jsx | 119 ++++++++++++++++++ .../shared/components/stats/course-stats.scss | 109 ++++++++++++++++ .../shared/reducers/dashboard-reducer.js | 23 ++++ .../server/express.js | 42 +++++++ .../server/models/assessment.js | 37 ++++++ 12 files changed, 424 insertions(+), 2 deletions(-) create mode 100644 packages/app/obojobo-repository/shared/components/course-score-data-dialog.jsx create mode 100644 packages/app/obojobo-repository/shared/components/stats/course-stats.jsx create mode 100644 packages/app/obojobo-repository/shared/components/stats/course-stats.scss diff --git a/packages/app/obojobo-repository/server/routes/stats.js b/packages/app/obojobo-repository/server/routes/stats.js index 766d8370ad..72828a9451 100644 --- a/packages/app/obojobo-repository/server/routes/stats.js +++ b/packages/app/obojobo-repository/server/routes/stats.js @@ -23,4 +23,18 @@ router res.render('pages/page-stats-server.jsx', props) }) +router + .route('/statsCourses') + .get([requireCurrentUser, requireCanViewStatsPage]) + .get((req, res) => { + const props = { + title: 'Stats', + currentUser: req.currentUser, + // must use webpackAssetPath for all webpack assets to work in dev and production! + appCSSUrl: webpackAssetPath('stats.css'), + appJsUrl: webpackAssetPath('stats.js') + } + res.render('pages/page-stats-server.jsx', props) + }) + module.exports = router diff --git a/packages/app/obojobo-repository/shared/actions/dashboard-actions.js b/packages/app/obojobo-repository/shared/actions/dashboard-actions.js index c9071c48eb..a49a6f4e70 100644 --- a/packages/app/obojobo-repository/shared/actions/dashboard-actions.js +++ b/packages/app/obojobo-repository/shared/actions/dashboard-actions.js @@ -2,7 +2,7 @@ const { MODE_RECENT, MODE_ALL, MODE_COLLECTION } = require('../repository-consta const debouncePromise = require('debounce-promise') const dayjs = require('dayjs') const advancedFormat = require('dayjs/plugin/advancedFormat') -const { apiGetAssessmentDetailsForDraft } = require('./shared-api-methods') +const { apiGetAssessmentDetailsForDraft, apiGetCourseDetailsForDraft } = require('./shared-api-methods') dayjs.extend(advancedFormat) // =================== API ======================= @@ -232,6 +232,13 @@ const showAssessmentScoreData = module => ({ promise: apiGetAssessmentDetailsForDraft(module.draftId) }) +const SHOW_COURSE_SCORE_DATA = 'SHOW_COURSE_SCORE_DATA' +const showCourseScoreData = module => ({ + type: SHOW_COURSE_SCORE_DATA, + meta: { module }, + promise: apiGetCourseDetailsForDraft(module.draftId) +}) + const RESTORE_VERSION = 'RESTORE_VERSION' const restoreVersion = (draftId, versionId) => ({ type: RESTORE_VERSION, @@ -664,6 +671,7 @@ module.exports = { IMPORT_MODULE_FILE, CHECK_MODULE_LOCK, SHOW_ASSESSMENT_SCORE_DATA, + SHOW_COURSE_SCORE_DATA, GET_DELETED_MODULES, GET_MODULES, BULK_RESTORE_MODULES, @@ -705,6 +713,7 @@ module.exports = { importModuleFile, checkModuleLock, showAssessmentScoreData, + showCourseScoreData, getDeletedModules, getModules, bulkRestoreModules diff --git a/packages/app/obojobo-repository/shared/actions/shared-api-methods.js b/packages/app/obojobo-repository/shared/actions/shared-api-methods.js index 0a990b25a2..d1279edaa9 100644 --- a/packages/app/obojobo-repository/shared/actions/shared-api-methods.js +++ b/packages/app/obojobo-repository/shared/actions/shared-api-methods.js @@ -21,7 +21,14 @@ const apiGetAssessmentDetailsForDraft = draftId => { const apiGetAssessmentDetailsForMultipleDrafts = draftIds => Promise.all(draftIds.map(id => apiGetAssessmentDetailsForDraft(id))).then(result => result.flat()) +const apiGetCourseDetailsForDraft = draftId => { + return fetch(`/api/courses/${draftId}`, defaultOptions()) + .then(res => res.json()) + .then(res => res.value) +} + module.exports = { apiGetAssessmentDetailsForMultipleDrafts, - apiGetAssessmentDetailsForDraft + apiGetAssessmentDetailsForDraft, + apiGetCourseDetailsForDraft } diff --git a/packages/app/obojobo-repository/shared/components/course-score-data-dialog.jsx b/packages/app/obojobo-repository/shared/components/course-score-data-dialog.jsx new file mode 100644 index 0000000000..5a1bfd97d5 --- /dev/null +++ b/packages/app/obojobo-repository/shared/components/course-score-data-dialog.jsx @@ -0,0 +1,29 @@ +require('./assessment-score-data-dialog.scss') + +const React = require('react') +const ModuleImage = require('./module-image') +const Button = require('./button') +const Loading = require('./loading') +const CourseStats = require('./stats/course-stats') +// const AssessmentStats = require('./stats/assessment-stats') + +const CourseScoreDataDialog = ({ draftId, title, onClose, isCoursesLoading, courses }) => { + return ( +
+
+ +
{title}
+ +
+
+ + { } + +
+
+ ) +} + +module.exports = CourseScoreDataDialog diff --git a/packages/app/obojobo-repository/shared/components/dashboard-hoc.js b/packages/app/obojobo-repository/shared/components/dashboard-hoc.js index a54ef88455..b536caa71b 100644 --- a/packages/app/obojobo-repository/shared/components/dashboard-hoc.js +++ b/packages/app/obojobo-repository/shared/components/dashboard-hoc.js @@ -31,6 +31,7 @@ const { moduleRemoveFromCollection, showVersionHistory, showAssessmentScoreData, + showCourseScoreData, restoreVersion, importModuleFile, checkModuleLock, @@ -70,6 +71,7 @@ const mapActionsToProps = { moduleRemoveFromCollection, showVersionHistory, showAssessmentScoreData, + showCourseScoreData, restoreVersion, importModuleFile, checkModuleLock, diff --git a/packages/app/obojobo-repository/shared/components/dashboard.jsx b/packages/app/obojobo-repository/shared/components/dashboard.jsx index 5001ae824b..b679f3210c 100644 --- a/packages/app/obojobo-repository/shared/components/dashboard.jsx +++ b/packages/app/obojobo-repository/shared/components/dashboard.jsx @@ -14,6 +14,7 @@ const MultiButton = require('./multi-button') const Search = require('./search') const ReactModal = require('react-modal') const AssessmentScoreDataDialog = require('./assessment-score-data-dialog') +const CourseScoreDataDialog = require('./course-score-data-dialog') const Spinner = require('./spinner') const Collection = require('./collection') const ModuleManageCollectionsDialog = require('./module-manage-collections-dialog') @@ -37,6 +38,7 @@ const renderOptionsDialog = (props, extension) => ( onClose={props.closeModal} showVersionHistory={props.showVersionHistory} showAssessmentScoreData={props.showAssessmentScoreData} + showCourseScoreData={props.showCourseScoreData} startLoadingAnimation={props.startLoadingAnimation} stopLoadingAnimation={props.stopLoadingAnimation} showModuleManageCollections={props.showModuleManageCollections} @@ -84,6 +86,18 @@ const renderAssessmentScoreDataDialog = props => { ) } +const renderCourseScoreDataDialog = props => { + return ( + + ) +} + const renderModalDialog = props => { let dialog let title @@ -155,6 +169,11 @@ const renderModalDialog = props => { dialog = renderAssessmentScoreDataDialog(props) break + case 'module-course-score-data': + title = 'Module Course Score Data' + dialog = renderCourseScoreDataDialog(props) + break + case 'module-manage-collections': title = 'Module Collections' dialog = renderModuleManageCollectionsDialog(props, extendedProps) diff --git a/packages/app/obojobo-repository/shared/components/module-options-dialog.jsx b/packages/app/obojobo-repository/shared/components/module-options-dialog.jsx index 57c1bb2a99..58587e5399 100644 --- a/packages/app/obojobo-repository/shared/components/module-options-dialog.jsx +++ b/packages/app/obojobo-repository/shared/components/module-options-dialog.jsx @@ -79,6 +79,18 @@ const ModuleOptionsDialog = props => (
View scores by student.
+
+ +
View scores by course.
+
+ {props.accessLevel !== MINIMAL && (
-
-
- - { } - +// { draftId, title, onClose, isCoursesLoading, courses } + +// const CourseScoreDataDialog = ({ draftId, title, onClose, isCoursesLoading, courses }) => { +// return ( +//
+//
+// +//
{title}
+// +//
+//
+// +// { } +// +//
+//
+// ) +// } + +class CourseScoreDataDialog extends React.Component { + constructor(props) { + super(props) + + this.baseUrl = `/api/assessments/${props.draftId}` // `${urlForEditor(props.editor, props.draftId)}` + + this.state = { + isMenuOpen: true, + isConfirmDialogOpen: false, + isLockDialogOpen: false, + isErrorDialogOpen: false, + editorUrl: null, + selectedIndex: -1 + } + + this.selectCourse = this.selectCourse.bind(this) + this.toggleMenu = this.toggleMenu.bind(this) + this.openConfirmDialog = this.openConfirmDialog.bind(this) + this.closeConfirmDialog = this.closeConfirmDialog.bind(this) + this.openErrorDialog = this.openErrorDialog.bind(this) + this.closeErrorDialog = this.closeErrorDialog.bind(this) + + this.menuRef = React.createRef() + } + + componentDidUpdate(prevProps) { + // When the list goes from empty to not-empty, we may choose to load the most recent data. + // if (!prevProps.courses.length && this.props.courses.length) { + // this.selectCourse(0) + // } + } + + editUrlForItem(index) { + return `${this.baseUrl}/course/${this.props.courses[index].contextId}/details` + } + + selectCourse(index) { + this.setState({ editorUrl: this.editUrlForItem(index) }) + this.setState({ selectedIndex: index }) + + // TODO: Get the data!!! + } + + toggleMenu() { + this.setState({ isMenuOpen: !this.state.isMenuOpen }) + } + + openConfirmDialog() { + this.setState({ isConfirmDialogOpen: true }) + } + + closeConfirmDialog() { + this.setState({ isConfirmDialogOpen: false }) + } + + openErrorDialog() { + this.setState({ isErrorDialogOpen: true }) + } + + closeErrorDialog() { + this.setState({ isErrorDialogOpen: false }) + } + + renderMenuToggleButton() { + return ( + + ) + } + + renderCourseMenu() { + return ( + +
+
+
+
Select a Course
+
Recently Accessed First
+ {this.renderMenuToggleButton()} +
+ {this.props.courses.map((course, index) => ( + + ))} +
+
{this.renderMenuToggleButton()}
+
+
+ ) + } + + render() { + const validCourseSelected = this.state.selectedIndex > -1 && this.state.selectedIndex < this.props.courses.length + const currentCourseTitle = validCourseSelected + ? `${this.props.courses[this.state.selectedIndex].contextTitle} (${this.props.courses[this.state.selectedIndex].contextLabel})` + : 'Select a Course to View Assessment Data' + const currentCourseAccessed = validCourseSelected + ? `Last Accessed ${dayjs(this.props.courses[this.state.selectedIndex].lastAccessed).format('MMM Do YYYY - h:mm A')}` + : '' + + return ( +
+
+ +
{this.props.title}
+ +
+
+ + {this.renderCourseMenu()} +
+
+ {currentCourseTitle} + { validCourseSelected ? ({currentCourseAccessed}) : ''} +
+