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

Dispatch Board QMS integration #323

Merged
merged 10 commits into from
May 19, 2021
8 changes: 7 additions & 1 deletion .env.local.template
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# REQUIRED | Port to listen on.
PORT=3000

# REQUIRED | Publicly or locally accessible URL. Leave as localhost if in development.
# REQUIRED | Publicly or locally accessible URL. Leave untouched if in development.
APP_URL=http://localhost:$PORT

# API URL which the SERVER contacts. Client-side never contacts API directly.
Expand Down Expand Up @@ -31,5 +31,11 @@ FR_STRIPE_API_SK=pk_XXXX_XXXXXXXXXXXXXXXXXXXXXXXX
# Donation API IP bans file. Leave undefined unless testing
# FR_STRIPE_BANS_FILE=/path/to/file.json

# API URL for the Queue Management System
QMS_API_URL=https://api.qms.fuelrats.dev

# API Token for the Queue Management System
QMS_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# Editor command to use when opening files from dev overlay. This is an optional dev variable.
REACT_EDITOR=code
9 changes: 9 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,13 @@ module.exports = {
},
},
},
overrides: [
{
files: ['src/pages/api/**/*.js'],
env: {
browser: false,
node: true,
},
},
],
}
25 changes: 9 additions & 16 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,24 @@
"version": "0.2.0",
"configurations": [
{
"name": "Next: Server",
"name": "Next: Attach Server",
"type": "node",
"request": "launch",
"envFile": "${workspaceFolder}/.env",
"program": "${workspaceFolder}/server/server.js",
"protocol": "inspector",
"console": "integratedTerminal",
"request": "attach",
"port": 9229,
},
{
"name": "Next: Server (Attach)",
"type": "node",
"request": "attach"
},
{
"name": "Next: Chrome",
"name": "Next: Attach Chrome",
"type": "chrome",
"request": "launch",
"url": "${config:debug.developurl}",
"request": "attach",
"port": 9222,
"url": "${config:develop.attachurl}",
"webRoot": "${workspaceFolder}"
}
],
"compounds": [
{
"name": "Next: Full",
"configurations": ["Next: Chrome", "Next: Server"]
"name": "Next: Attach",
"configurations": ["Next: Attach Server", "Next: Attach Chrome"]
}
]
}
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,5 @@
"stylelint.packageManager": "yarn",
"stylelint.reportNeedlessDisables": true,
"cSpell.language": "en,en-US",
"develop.attachurl": "http://localhost:3000/"
}
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ For detailed rules of this file, see [Changelog Rules](#changelog-rules)
### ✨ Added
* Added controls for rats to opt-in to Odyssey rescues. - [#320][]
* Added `odyssey` field to registration page. - [#320][]
* Added basic QMS queue counter to Dispatch Board. - [#323][]


### ⚡ Changed
Expand All @@ -52,6 +53,7 @@ For detailed rules of this file, see [Changelog Rules](#changelog-rules)
[#316]: https://github.com/fuelRats/fuelrats.com/pull/316
[#317]: https://github.com/fuelRats/fuelrats.com/pull/317
[#320]: https://github.com/fuelRats/fuelrats.com/pull/320
[#323]: https://github.com/fuelRats/fuelrats.com/pull/323
[2.13.0]: https://github.com/FuelRats/fuelrats.com/compare/v2.12.10...v2.13.0


Expand Down
11 changes: 11 additions & 0 deletions src/components/DispatchTable/DispatchTable.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types'
import { useSelector } from 'react-redux'

import { useRescueQueueCount } from '~/hooks/rescueHooks'
import { selectDispatchBoard } from '~/store/selectors/dispatch'

import styles from './DispatchTable.module.scss'
Expand All @@ -16,6 +17,7 @@ function DispatchTable (props) {
} = props

const rescueIds = useSelector(selectDispatchBoard)
const queueLength = useRescueQueueCount()

return (
<section className={[styles.dispatchTable, className]}>
Expand Down Expand Up @@ -43,6 +45,15 @@ function DispatchTable (props) {
}
</tbody>
</table>
{
queueLength > 0 && (
<div className={styles.queueLength}>
{'+'}
<b>{queueLength}</b>
<small>{' IN QUEUE'}</small>
</div>
)
}
</section>
)
}
Expand Down
11 changes: 11 additions & 0 deletions src/components/DispatchTable/DispatchTable.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,14 @@
background-color: rgba(240, 230, 140, 0.25);
opacity: 0.25;
}


.queueLength {
float: right;

padding: 2px 8px;

border: 1px solid var(--table-border-color);

background: $black;
}
14 changes: 7 additions & 7 deletions src/components/Switch/Switch.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,22 @@ function Switch (props) {
disabled,
id,
label,
onClick,
onChange,
...inputProps
} = props

const [loading, setLoading] = useState(false)

const handleClick = useCallback(async (event) => {
const handleChange = useCallback(async (event) => {
if (!isAsync) {
onClick?.(event)
onChange?.(event)
return
}

setLoading(true)
await onClick?.(event)
await onChange?.(event)
setLoading(false)
}, [isAsync, onClick])
}, [isAsync, onChange])

let icon = 'times'
if (loading) {
Expand All @@ -52,7 +52,7 @@ function Switch (props) {
disabled={loading || disabled}
id={id}
type="checkbox"
onChange={handleClick} />
onChange={handleChange} />

<span className={styles.slider} />
<FontAwesomeIcon
Expand All @@ -78,7 +78,7 @@ Switch.propTypes = {
disabled: PropTypes.bool,
id: PropTypes.string.isRequired,
label: PropTypes.node,
onClick: PropTypes.func,
onChange: PropTypes.func.isRequired,
}


Expand Down
19 changes: 19 additions & 0 deletions src/helpers/apiMiddlewares.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { isRequired } from '@fuelrats/validation-util'
import { HttpStatus } from '@fuelrats/web-util/http'




export function methodRouter (handlers = isRequired('handlers')) {
return async (req, res) => {
const methodHandler = handlers[req.method]

if (typeof methodHandler === 'function') {
await methodHandler(req, res)
return
}

res.setHeader('Allow', Object.keys(handlers))
res.status(HttpStatus.METHOD_NOT_ALLOWED).end(`Method ${req.method} Not Allowed`)
}
}
34 changes: 33 additions & 1 deletion src/hooks/rescueHooks.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useMemo } from 'react'
import axios from 'axios'
import { useMemo, useState, useEffect } from 'react'

import { getLanguage } from '~/data/LanguageList'
import { getPlatform } from '~/data/PlatformList'
Expand Down Expand Up @@ -27,3 +28,34 @@ export const usePlatformData = (rescue) => {
return getPlatform(rescue.attributes.platform)
}, [rescue.attributes.platform])
}


export const useRescueQueueCount = () => {
const [rescueCount, setCount] = useState(0)

useEffect(
() => {
let timeout = null

const fetchData = async () => {
const { data } = await axios.get('/api/qms/queue')

setCount(data.data.queueLength)

timeout = setTimeout(fetchData, data.meta.maxAge)
}

fetchData()

return () => {
if (timeout) {
clearTimeout(timeout)
}
}
},
[],
)


return rescueCount
}
54 changes: 54 additions & 0 deletions src/pages/api/qms/queue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { HttpStatus } from '@fuelrats/web-util/http'
import axios from 'axios'

import { methodRouter } from '~/helpers/apiMiddlewares'


// 10 second maxAge
const cacheMaxAge = 10000

const cache = {
lastCheck: 0,
count: 0,
}


async function Queue (req, res) {
const nowTime = Date.now()

if (nowTime - cache.lastCheck >= cacheMaxAge) {
cache.lastCheck = nowTime

const result = await axios.get(
`${process.env.QMS_API_URL}/api/v1/queue`,
{
headers: {
Authorization: `Bearer ${process.env.QMS_API_TOKEN}`,
},
},
)

const rescueQueue = result.data.filter((rescue) => {
return !rescue.pending && !rescue.in_progress
})

cache.count = rescueQueue.length
}


return res.status(HttpStatus.OK).json({
data: {
queueLength: cache.count,
},
meta: {
age: nowTime - cache.lastCheck,
maxAge: cacheMaxAge,
},
})
}



export default methodRouter({
GET: Queue,
})