Skip to content
This repository was archived by the owner on Mar 12, 2025. It is now read-only.

Commit 8b1d997

Browse files
committed
adding in group member extractor
1 parent 68fc59a commit 8b1d997

File tree

7 files changed

+441
-6
lines changed

7 files changed

+441
-6
lines changed

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,5 +115,4 @@ dist
115115
.yarn/install-state.gz
116116
.pnp.*
117117

118-
1-create-users.csv
119-
2-users-with-skills.csv
118+
*.csv

config/custom-environment-variables.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@
88
"AUTH0_CLIENT_SECRET": "AUTH0_CLIENT_SECRET",
99
"AUTH0_AUDIENCE": "AUTH0_AUDIENCE",
1010
"TOKEN_CACHE_TIME": "TOKEN_CACHE_TIME",
11-
"SLEEP_LENGTH": "SLEEP_LENGTH"
11+
"SLEEP_LENGTH": "SLEEP_LENGTH",
12+
"GROUPS_API_URL": "GROUPS_API_URL",
13+
"USERS_API_URL": "USERS_API_URL",
14+
"MEMBERS_API_URL": "MEMBERS_API_URL"
1215
}

config/default.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@
88
"AUTH0_CLIENT_SECRET": "",
99
"AUTH0_AUDIENCE": "https://u-bahn.topcoder.com",
1010
"TOKEN_CACHE_TIME": 86400000,
11-
"SLEEP_LENGTH": 3000
11+
"SLEEP_LENGTH": 3000,
12+
"GROUPS_API_URL": "https://api.topcoder-dev.com/v5/groups",
13+
"USERS_API_URL": "https://api.topcoder-dev.com/v3/users",
14+
"MEMBERS_API_URL": "https://api.topcoder-dev.com/v5/members"
1215
}

create-skills.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ async function createSkill() {
3232
console.log(`${i}: ${name}`)
3333

3434
try {
35-
3635
await axios.post(url, {
3736
skillProviderId,
3837
name
@@ -43,6 +42,7 @@ async function createSkill() {
4342
})
4443
} catch (error) {
4544
console.log(`Error for skill: '${name}'`)
45+
console.log(error)
4646
console.log(error.message)
4747
fails[fails.length] = { postion: i, name}
4848
}

group-members-with-skills.js

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
/**
2+
* Script that:
3+
* - reads members in a group
4+
* - reads skills of the earlier members
5+
* - prepares a CSV file
6+
* for bulk upload to U-Bahn
7+
*
8+
* ! NOTE: Set the environment variables in the .env file.
9+
* ! The list of environment variables can be found in the config/default.js file
10+
*
11+
* Usage:
12+
* $ node group-members-with-skills.js --groupName="Night Owls" --skillProviderName="EMSI"
13+
*
14+
* where
15+
* - groupName: Name of the group from which we need to fetch members
16+
* - skillProviderName: The skill provider name to be used for the skills associated with the members
17+
*/
18+
19+
//require('dotenv').config()
20+
const axios = require('axios')
21+
const config = require('config')
22+
const { argv } = require('yargs')
23+
const m2mAuth = require('tc-core-library-js').auth.m2m
24+
const _ = require('lodash')
25+
const { parse } = require('json2csv')
26+
const fs = require('fs')
27+
28+
const m2m = m2mAuth(_.pick(config, ['AUTH0_URL', 'AUTH0_AUDIENCE', 'TOKEN_CACHE_TIME', 'AUTH0_PROXY_SERVER_URL']))
29+
30+
const USAGE = 'node group-members-with-skills.js --groupName="<group_name>" --skillProviderName="<skillprovider_name>". Don\'t forget the quotes for the values.'
31+
32+
let token
33+
34+
async function sleep(ms) {
35+
return new Promise(resolve => setTimeout(resolve, ms));
36+
}
37+
38+
/**
39+
* Get M2M token.
40+
* @returns {Promise<unknown>}
41+
*/
42+
async function getM2Mtoken () {
43+
if (!token) {
44+
console.log(config)
45+
token = m2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET)
46+
}
47+
48+
return token
49+
}
50+
51+
/**
52+
* Searches for the group details with the given group name
53+
* @param {String} groupName The group name
54+
*/
55+
async function getGroupIdFromname (groupName) {
56+
const token = await getM2Mtoken()
57+
58+
try {
59+
const res = await axios.get(config.GROUPS_API_URL, {
60+
params: {
61+
name: groupName
62+
},
63+
headers: {
64+
Authorization: `Bearer ${token}`
65+
}
66+
})
67+
68+
return res.data
69+
} catch (error) {
70+
console.log(`Error when fetching group id for name ${groupName}`)
71+
console.log(error)
72+
73+
throw error
74+
}
75+
}
76+
77+
/**
78+
* Returns members in the group identified by the groupId
79+
* @param {String} groupId The group id
80+
*/
81+
async function getMembersInGroup (groupId) {
82+
const url = `${config.GROUPS_API_URL}/${groupId}/members`
83+
const perPage = 12
84+
let page = 1
85+
const members = []
86+
let once = false
87+
88+
const token = await getM2Mtoken()
89+
90+
while (true) {
91+
try {
92+
const res = await axios.get(url, {
93+
params: {
94+
page,
95+
perPage,
96+
},
97+
headers: {
98+
Authorization: `Bearer ${token}`
99+
}
100+
})
101+
102+
if (!once) {
103+
const total = res.headers['x-total']
104+
console.log(`Discovered ${total} members in the group...`)
105+
106+
if (total > 0) {
107+
console.log('Fetching all of them...')
108+
}
109+
110+
once = true
111+
}
112+
113+
if (res.data.length > 0) {
114+
console.log(res.data)
115+
members.push(...res.data)
116+
}
117+
118+
if (res.data.length !== perPage) {
119+
break
120+
}
121+
122+
page += 1
123+
} catch (error) {
124+
console.log(`Error when fetching members in group with id ${groupId} at page ${page} and per page ${perPage}`)
125+
console.log(error)
126+
127+
throw error
128+
}
129+
}
130+
131+
return members
132+
}
133+
134+
/**
135+
* Returns the member handle for the member id
136+
* @param {Number} memberId The member id
137+
*/
138+
async function getMemberHandle (memberId) {
139+
const token = await getM2Mtoken()
140+
141+
try {
142+
const res = await axios.get(config.USERS_API_URL, {
143+
params: {
144+
filter: `id=${memberId}`
145+
},
146+
headers: {
147+
Authorization: `Bearer ${token}`
148+
}
149+
})
150+
console.log(res.data.result.content)
151+
const user = _.pick(_.get(res, 'data.result.content[0]', {}), ['handle', 'firstName', 'lastName', 'email'] )
152+
153+
return user
154+
} catch (error) {
155+
console.log(`Error getting the member handle for member with id ${memberId}`)
156+
console.log(error)
157+
158+
throw error
159+
}
160+
}
161+
162+
/**
163+
* Returns the member's skills
164+
* @param {String} handle The member's handle
165+
*/
166+
async function getMemberSkills (handle) {
167+
const url = `${config.MEMBERS_API_URL}/${handle}/skills`
168+
169+
const token = await getM2Mtoken()
170+
171+
try {
172+
const res = await axios.get(url, {
173+
params: {
174+
fields: 'skills'
175+
},
176+
headers: {
177+
Authorization: `Bearer ${token}`
178+
}
179+
})
180+
181+
const { skills } = res.data
182+
183+
const skillDetails = Object.keys(skills).map(key => ({
184+
name: skills[key].tagName,
185+
score: skills[key].score
186+
}))
187+
188+
return skillDetails
189+
} catch (error) {
190+
if (error.response.status === 404) {
191+
// No skills exist for the user
192+
return []
193+
}
194+
195+
console.log(`Error getting the member's skills for member with handle ${handle}`)
196+
console.log(error)
197+
198+
throw error
199+
}
200+
}
201+
202+
/**
203+
* Returns CSV string for an array of objects
204+
* @param {Array} data Array of objects
205+
*/
206+
async function getCSV (data) {
207+
const columns = ['handle', 'firstName', 'lastName', 'email', 'skillProviderName', 'skillName', 'metricValue']
208+
209+
try {
210+
const csv = parse(data, { fields: columns })
211+
212+
return csv
213+
} catch (error) {
214+
console.log('Error converting data to CSV format')
215+
console.log(error)
216+
217+
throw error
218+
}
219+
}
220+
221+
async function start () {
222+
const users = []
223+
const usersWithSkills = []
224+
const { groupName, skillProviderName } = argv
225+
226+
if (!groupName) {
227+
console.log(`Missing group name. Correct usage: ${USAGE}`)
228+
return
229+
} else if (!skillProviderName) {
230+
console.log(`Missing skill provider name. Correct usage: ${USAGE}`)
231+
return
232+
}
233+
234+
console.log(`Searching for id of group named ${groupName}...`)
235+
236+
const groups = await getGroupIdFromname(groupName)
237+
238+
if (groups.length !== 1) {
239+
console.log(`There are ${groups.length} groups with that name. Aborting`)
240+
return
241+
}
242+
243+
const { id: groupId } = groups[0]
244+
245+
console.log(`Group found with id ${groupId}. Fetching members in this group...`)
246+
247+
const members = await getMembersInGroup(groupId)
248+
249+
if (members.length === 0) {
250+
console.log(`There are no members in group with name ${groupName}, having group id ${groupId}. Aborting`)
251+
252+
return
253+
}
254+
255+
console.log('Fetching the member handles for each member found in the group...')
256+
257+
const membersFiltered = _.filter(members, (m) => {
258+
return (m.membershipType === 'user')
259+
})
260+
261+
memberIds = membersFiltered.map(m => m.memberId)
262+
//const memberIds = [8547899]
263+
264+
for (let i = 0; i < memberIds.length; i++) {
265+
const user = await getMemberHandle(memberIds[i])
266+
console.log(`pushing '${user.handle}' into stack`)
267+
users.push(user)
268+
console.log(`throttling call for ${config.SLEEP_LENGTH}s`)
269+
await sleep(config.SLEEP_LENGTH)
270+
}
271+
272+
console.log('Fetching the skills for each member...')
273+
274+
for (let i = 0; i < users.length; i++) {
275+
const handle = users[i].handle
276+
const skills = await getMemberSkills(handle)
277+
278+
if (skills.length === 0) {
279+
console.log(`Member with handle ${handle} has no skills. Skipping...`)
280+
continue
281+
}
282+
283+
for (let j = 0; j < skills.length; j++) {
284+
usersWithSkills.push({
285+
handle,
286+
firstName,
287+
lastName,
288+
email,
289+
skillProviderName,
290+
skillName: skills[j].name,
291+
metricValue: '' + skills[j].score
292+
})
293+
}
294+
}
295+
296+
console.log('Converting data to CSV...')
297+
298+
const csv = await getCSV(usersWithSkills)
299+
300+
console.log('Saving CSV data to file...')
301+
302+
const date = (new Date()).toISOString()
303+
304+
fs.writeFileSync(`skill-data-${date}.csv`, csv)
305+
306+
console.log(`File skill-data-${date}.csv generated successfully.`)
307+
console.log('~~FIN~~')
308+
}
309+
310+
start()

0 commit comments

Comments
 (0)