Skip to content
Merged
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
1 change: 1 addition & 0 deletions api/src/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const settings = require('./settings')
*/
router.get('/users', users.list)
router.get('/users/stats', users.stats)
router.post('/users/names', users.getNames)
router.get('/users/:id', user.get)
router.put('/users/:id', user.put)
router.get('/campaigns/:tasker_id-:tm_id', campaign)
Expand Down
30 changes: 29 additions & 1 deletion api/src/routes/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,35 @@ async function list (req, res) {
}
}

/**
* Get names route
* /users/names
*
* List the osm names for a set of osm ids. This is a helper route that is used by
* getAllTeams action.
*
* @param req
* @param res
* @returns {Promise<*>}
* @async
*/
async function getNames (req, res) {
const { body } = req
try {
const { ids } = body
const data = await db
.select('osm_id', 'full_name')
.from('users')
.whereIn('osm_id', ids)
res.send(data)
} catch (err) {
console.error(err)
return res.boom.badRequest('Could not get user names')
}
}

module.exports = {
stats,
list
list,
getNames
}
16 changes: 16 additions & 0 deletions api/tests/api/users.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,22 @@ test('Pull all users with stats', async (t) => {
t.false(Number.isNaN(response.body.records[0].edit_count))
})

test.serial('Get user names from osm ids', async (t) => {
const allUsersResponse = await request(app)
.get('/scoreboard/api/users/stats')
const ids = allUsersResponse.body.records.map(prop('osm_id'))
const params = { ids }
const response = await request(app)
.post('/scoreboard/api/users/names').send(params)
.expect(200)
const records = response.body
t.is(records.length, ids.length)
records.forEach(({ osm_id, full_name }) => {
t.truthy(osm_id)
t.truthy(full_name)
})
})

test('Sort users by most recently active', async (t) => {
const response = await request(app)
.get('/scoreboard/api/users/stats/?q=&page=1&sortType=Most%20recent&active=false')
Expand Down
6 changes: 5 additions & 1 deletion components/common/Table.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ function selectCellFormatter (datatype, idMap, countryMap, campaignMap) {
}

function prepareAllHeaders (table) {
const headers = glossary.filter(term => Object.keys(table.headers).includes(term.id))
const tableHeaders = Object.keys(table.headers)
const headers = glossary.filter(term => tableHeaders.includes(term.id))
if (headers.length !== tableHeaders.length) {
throw new Error('Header(s) are missing from the i18n glossary')
}
// appends a boolean property to headers to indicate whether the tooltip is showing
headers.map(header => (
table.displaysTooltip.includes(header.id) ? (
Expand Down
20 changes: 20 additions & 0 deletions lib/i18n/glossary_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,25 @@
"id": "campaign-priority",
"name": "Priority",
"description": "Priority of campaign for mappers on team (High/Medium/Low)"
},
{
"id": "team-name",
"name": "Team Name",
"description": "Team Name"
},
{
"id": "#-members",
"name": "# of Members",
"description": "Number of Members"
},
{
"id": "moderator-names",
"name": "Moderator Names",
"description": "Moderator Names"
},
{
"id":"team-hashtag",
"name":"Hashtag",
"description":"Hashtag for this team"
}
]
25 changes: 23 additions & 2 deletions lib/store/actions/teams.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
import { flatten, fromPairs } from 'ramda'
import fetch from '../../utils/api'

export default store => ({
getAllTeams (state) {
return fetch('/api/teams')
.then(async res => {
const records = await res.json()
.then(res => res.json())
.then(async teams => {
// enhance the teams listing with moderator names
const moderatorIds = new Set(flatten(teams.map(team => team.moderators)))
const fetchParams = { ids: Array.from(moderatorIds) }
const fetchInit = {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify(fetchParams)
}
const moderatorNames = await fetch('/api/users/names', fetchInit)
.then(res => res.json())
.then(records => fromPairs(records.map(({ osm_id, full_name }) => [osm_id, full_name])))
return teams.map(team => ({
...team,
moderators: fromPairs(team.moderators.map(osmId => [osmId, moderatorNames[osmId]]))
}))
})
.then(records => {
store.setState({ teams: { records } })
})
.catch(err => {
Expand Down
13 changes: 0 additions & 13 deletions lib/utils/format.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,3 @@ export function formatEditTimeDescription (timeVar) {
export function formatUpdateDescription (timeVar) {
return `${distanceInWords(timeVar, new Date())} ago`
}

/**
* Prefix with '#' for consistent display of hashtag.
*
* @param {string} hashtag
* @returns {string}
*/
export function normalizeHashtag (hashtag) {
if (/^#/.test(hashtag)) {
return hashtag
}
return `#${hashtag}`
}
4 changes: 2 additions & 2 deletions pages/team.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import TeamStatsTable from '../components/campaign/CampaignTable'
import Table from '../components/common/Table'
import Blurb from '../components/teams/TeamBlurb'
import Link from '../components/Link'
import { formatDecimal, normalizeHashtag } from '../lib/utils/format'
import { formatDecimal } from '../lib/utils/format'
import { actions } from '../lib/store'
import {
calculateBlurbStats,
Expand Down Expand Up @@ -113,7 +113,7 @@ export class Team extends Component {
<ul className='list--two-column'>
<li>
<span className='list-label'>hashtag</span>
<strong>{ normalizeHashtag(hashtag) }</strong>
<strong>{ hashtag }</strong>
</li>
<li>
<span className='list-label'>created</span>
Expand Down
24 changes: 16 additions & 8 deletions pages/teams.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import { actions } from '../lib/store'
import join from 'url-join'
import Table from '../components/common/Table'
import TeamsConnectBanner from '../components/TeamConnectBanner'

import { APP_URL_PREFIX } from '../api/src/config'

const tableSchema = {
'headers': {
'name': { type: 'teamlink', accessor: 'name' },
'hashtags': { type: 'string', accessor: 'hashtag' }
'team-name': { type: 'teamlink', accessor: 'name' },
'#-members': { type: 'number', accessor: 'memberCount' },
'team-hashtag': { type: 'string', accessor: 'hashtag' },
'moderator-names': { type: 'string', accessor: 'moderatorNames' }
},
columnOrder: [ 'name', 'hashtags' ],
columnOrder: [ 'team-name', '#-of-members', 'team-hashtag', 'moderator-names' ],
'displaysTooltip': [
'hashtags'
'team-hashtag'
]
}

Expand Down Expand Up @@ -75,13 +76,20 @@ class Teams extends Component {

renderList () {
const { teams } = this.state

if (!teams || !teams.length) return

const tableData = teams.map(team => {
const memberCount = team.members.length
const moderatorNames = Object.values(team.moderators).join(', ')
return {
...team,
memberCount,
moderatorNames
}
})
let idMap = Object.assign(...teams.map(({ id, name }) => ({ [name]: id })))
return (
<div>
<Table tableSchema={tableSchema} data={teams} idMap={idMap} />
<Table tableSchema={tableSchema} data={tableData} idMap={idMap} />
</div>
)
}
Expand Down