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

Hall of fame #43

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion api/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const auth = require('./auth')
const assignment = require('./assignment')
const user = require('./user')
const lectures = require('./lectures')
const stats = require('./stats')
const router = express.Router()

router.get('/', (req, res) => {
Expand All @@ -18,5 +19,6 @@ module.exports = {
'/api/auth': auth,
'/api/assignment': assignment,
'/api/user': user,
'/api/lecture': lectures
'/api/lecture': lectures,
'/api/stats': stats
}
38 changes: 38 additions & 0 deletions api/routes/stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const express = require('express')
const router = express.Router()

const { User, ParameterizedAssignment, sequelize } = require('blockchain-course-db').models

const getRanking = async (limit = 10, order = 'ASC') => {
let res = await ParameterizedAssignment.findAll({
include: [{
model: User,
as: 'student'
}],
attributes: ['ParameterizedAssignment.studentId', 'student.id', [sequelize.fn('COUNT', 'solved'), 'solved']],
where: { solved: true },
group: ['ParameterizedAssignment.studentId', 'student.id', 'ParameterizedAssignment.solved'],
limit,
order: [[sequelize.fn('count', sequelize.col('solved')), order]]
})

return res.map(row => (
{
id: row.dataValues.student.id,
username: row.dataValues.student.username,
email: row.dataValues.student.email,
solved: row.dataValues.solved
}
))
}

router.get('/top', async (req, res, next) => {
try {
const users = await getRanking(10, 'DESC')
return res.json({ success: true, users })
} catch (err) {
next(err)
}
})

module.exports = router
4 changes: 3 additions & 1 deletion app/src/actions/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ const types = [
'LOGOUT_SUCCESS',
'UNAUTHORIZED_ACTION',
'REQUEST_STARTED',
'REQUEST_FINISHED'
'REQUEST_FINISHED',
'GET_TOP_USER',
'GET_TOP_USER_SUCCESS'
]

const objTypes = buildActionTypes(types)
Expand Down
4 changes: 3 additions & 1 deletion app/src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import assignmentActions from './assignments'
import { closeToast, notify } from './notifications'
import { requestStart, requestFinish } from './requests'
import userActions from './user'
import statsActions from './stats'

export {
lectureGroupsActions,
Expand All @@ -13,5 +14,6 @@ export {
notify,
requestStart,
requestFinish,
userActions
userActions,
statsActions
}
12 changes: 12 additions & 0 deletions app/src/actions/stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

import { buildActions } from '../utils/actions'

import types from './actionTypes'

const actions = buildActions({
getTopUsers: types.GET_TOP_USER,
getTopUsersSuccess: types.GET_TOP_USER_SUCCESS,
topUsers: ['getTopUsers', 'getTopUsersSuccess', 'stats/top']
})

export default actions
4 changes: 3 additions & 1 deletion app/src/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import singleAssignment from './assignment'
import notification from './notifications'
import request from './requests'
import auth from './auth'
import stats from './stats'

const app = combineReducers({
user,
Expand All @@ -20,7 +21,8 @@ const app = combineReducers({
request,
auth,
groups,
group
group,
stats
})

export default app
13 changes: 13 additions & 0 deletions app/src/reducers/stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import types from '../actions/actionTypes'

import { createReducer } from '../utils/reducers'

const defaultState = {
topUsers: []
}

const auth = createReducer(defaultState, {
[types.GET_TOP_USER_SUCCESS]: (state, action) => { return { ...state, topUsers: [ ...action.payload.data.users ] } }
})

export default auth
11 changes: 10 additions & 1 deletion app/src/routes/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import AssignmentList from 'views/Assignments/AssignmentList'
import SingleAssignment from 'views/Assignments/Assignment'
import LoginCallback from 'views/Callbacks/LoginCallback.js'
import Home from 'views/Home/Home.js'
import HallOfFame from 'views/Stats/HallOfFame'

import {
Class,
Assignment
Assignment,
Poll
} from '@material-ui/icons/'

const appRoutes = [
Expand Down Expand Up @@ -48,6 +50,13 @@ const appRoutes = [
{
path: '/loginCallback',
component: LoginCallback
},
{
path: '/hall-of-fame',
sidebarName: 'Hall of Fame',
icon: Poll,
component: HallOfFame,
show: true
}
]

Expand Down
72 changes: 72 additions & 0 deletions app/src/views/Stats/HallOfFame.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { withStyles } from '@material-ui/core/styles'
import Table from '@material-ui/core/Table'
import TableBody from '@material-ui/core/TableBody'
import TableCell from '@material-ui/core/TableCell'
import TableHead from '@material-ui/core/TableHead'
import TableRow from '@material-ui/core/TableRow'

import {
statsActions
} from '../../actions'

const topUsers = statsActions.topUsers

const styles = theme => ({
jumbotron: {
fontSize: '100%'
},
button: {
margin: theme.spacing.unit
},
buttonFirst: {
marginLeft: 0
}
})

class HallOfFame extends React.Component {
componentDidMount () {
this.props.actions.topUsers().catch(e => console.log(e))
}

render () {
return (
<div>
<Table>
<TableHead>
<TableRow>
<TableCell>Username</TableCell>
<TableCell>Total Assignments Solved</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.props.stats.topUsers.map(user => (
<TableRow key={user.id}>
<TableCell component='th' scope='row'>
{user.username}
</TableCell>
<TableCell>{user.solved}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)
}
}

const mapStateToProps = (state, ownProps) => {
return {
stats: state.stats
}
}

const mapDispatchToProps = dispatch => {
return {
actions: bindActionCreators({ topUsers }, dispatch)
}
}

export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(HallOfFame))