Skip to content

Commit ecd93dc

Browse files
authored
Merge pull request #629 from topcoder-platform/profiles-app
MP-1 Standup member profiles in platform UI
2 parents 9c0feda + b24db75 commit ecd93dc

File tree

151 files changed

+5424
-67
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

151 files changed

+5424
-67
lines changed

.circleci/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,15 @@ workflows:
237237
ignore:
238238
- master
239239
- qa
240+
- profiles-app
240241

241242
- build-qa:
242243
context : org-global
243244
filters:
244245
branches:
245246
only:
246247
- qa
248+
- profiles-app
247249

248250
- build-prod:
249251
context : org-global
@@ -269,6 +271,7 @@ workflows:
269271
branches:
270272
only:
271273
- qa
274+
- profiles-app
272275

273276
- deployProd:
274277
context : org-global

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"country-calling-code": "0.0.3",
3838
"crypto-js": "^4.1.1",
3939
"customize-cra": "^1.0.0",
40+
"date-fns": "^2.30.0",
4041
"dompurify": "^2.4.0",
4142
"draft-js": "^0.10.4",
4243
"draft-js-export-html": "^1.2.0",
@@ -154,6 +155,8 @@
154155
"@types/react-redux-toastr": "^7.6.2",
155156
"@types/react-router-dom": "^5.3.3",
156157
"@types/redux-actions": "2.6.2",
158+
"@types/redux-logger": "^3.0.9",
159+
"@types/redux-promise": "^0.5.29",
157160
"@types/sanitize-html": "^2.6.2",
158161
"@types/segment-analytics": "^0.0.34",
159162
"@types/systemjs": "^6.1.1",

src/apps/platform/src/platform.routes.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { devCenterRoutes } from '~/apps/dev-center'
44
import { gamificationAdminRoutes } from '~/apps/gamification-admin'
55
import { earnRoutes } from '~/apps/earn'
66
import { selfServiceRoutes } from '~/apps/self-service'
7+
import { profilesRoutes } from '~/apps/profiles'
78

89
const Home: LazyLoadedComponent = lazyLoad(() => import('./routes/home'), 'HomePage')
910

@@ -24,5 +25,6 @@ export const platformRoutes: Array<PlatformRoute> = [
2425
...earnRoutes,
2526
...learnRoutes,
2627
...gamificationAdminRoutes,
28+
...profilesRoutes,
2729
...homeRoutes,
2830
]

src/apps/profiles/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Instructions for Running the Member Profiles Tool Locally
2+
3+
## Member Profiles

src/apps/profiles/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './src'
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { FC, useContext } from 'react'
2+
import { Outlet, Routes } from 'react-router-dom'
3+
4+
import { routerContext, RouterContextData } from '~/libs/core'
5+
6+
import { toolTitle } from './profiles.routes'
7+
import { ProfileSwr } from './lib'
8+
9+
const ProfilesApp: FC<{}> = () => {
10+
const { getChildRoutes }: RouterContextData = useContext(routerContext)
11+
12+
return (
13+
<ProfileSwr>
14+
<Outlet />
15+
<Routes>
16+
{getChildRoutes(toolTitle)}
17+
</Routes>
18+
</ProfileSwr>
19+
)
20+
}
21+
22+
export default ProfilesApp
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
@import "@libs/ui/styles/includes";
2+
3+
.container {
4+
display: flex;
5+
flex-direction: column;
6+
7+
.content {
8+
.contentHeader {
9+
display: flex;
10+
justify-content: space-between;
11+
align-items: center;
12+
border-bottom: 1px solid $black-10;
13+
margin: $sp-4 0;
14+
padding-bottom: $sp-2;
15+
16+
.contentHeaderActions {
17+
button {
18+
margin-right: $sp-2;
19+
20+
&:last-child {
21+
margin-right: 0;
22+
}
23+
}
24+
}
25+
}
26+
27+
.contentBody {
28+
height: 385px;
29+
overflow: auto;
30+
31+
@include ltelg {
32+
height: auto;
33+
}
34+
}
35+
}
36+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/* eslint-disable complexity */import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react'
2+
import { bind, isEmpty, keys } from 'lodash'
3+
import Highcharts from 'highcharts'
4+
import HighchartsReact from 'highcharts-react-official'
5+
6+
import { BaseModal, Button, LoadingSpinner } from '~/libs/ui'
7+
import {
8+
MemberStats,
9+
ratingToCSScolor,
10+
StatsHistory,
11+
UserProfile,
12+
UserStatsDistributionResponse,
13+
UserStatsHistory,
14+
useStatsDistribution,
15+
useStatsHistory,
16+
} from '~/libs/core'
17+
18+
import { numberToFixed } from '../../lib'
19+
20+
import { RATING_CHART_CONFIG, RATING_DISTRO_CHART_CONFIG } from './chart-configs'
21+
import styles from './AssemblyDetailsModal.module.scss'
22+
23+
type SRMViewTypes = 'STATISTICS' | 'CHALLENGES DETAILS'
24+
25+
interface AssemblyDetailsModalProps {
26+
isAssemblyDetailsOpen: boolean
27+
onClose: () => void
28+
assemblyStats: MemberStats | undefined
29+
profile: UserProfile | undefined
30+
}
31+
32+
const AssemblyDetailsModal: FC<AssemblyDetailsModalProps> = (props: AssemblyDetailsModalProps) => {
33+
const [viewType, setviewType]: [SRMViewTypes, Dispatch<SetStateAction<SRMViewTypes>>]
34+
= useState<SRMViewTypes>('STATISTICS')
35+
36+
const statsHistory: UserStatsHistory | undefined = useStatsHistory(props.profile?.handle)
37+
38+
const ratingHistoryOptions: Highcharts.Options | undefined = useMemo(() => {
39+
const assemblyHistory: Array<StatsHistory> | undefined
40+
= statsHistory?.DEVELOP?.subTracks?.find(subTrack => subTrack.name === 'ASSEMBLY_COMPETITION')?.history
41+
|| []
42+
const options: Highcharts.Options = RATING_CHART_CONFIG
43+
44+
if (!assemblyHistory.length) return undefined
45+
46+
options.series = [{
47+
data: assemblyHistory.sort((a, b) => b.date - a.date)
48+
.map((assemblyChallenge: StatsHistory) => ({
49+
name: assemblyChallenge.challengeName,
50+
x: assemblyChallenge.ratingDate,
51+
y: assemblyChallenge.newRating,
52+
})),
53+
name: 'Assembly Competition Rating',
54+
type: 'spline',
55+
}]
56+
57+
return options
58+
}, [statsHistory])
59+
60+
const memberStatsDist: UserStatsDistributionResponse | undefined = useStatsDistribution({
61+
filter: 'track=DEVELOP&subTrack=ASSEMBLY_COMPETITION',
62+
})
63+
64+
const ratingDistributionOptions: Highcharts.Options | undefined = useMemo(() => {
65+
const ratingDistro: { [key: string]: number } = memberStatsDist?.distribution || {}
66+
const options: Highcharts.Options = RATING_DISTRO_CHART_CONFIG
67+
68+
if (isEmpty(ratingDistro)) return undefined
69+
70+
options.series = keys(ratingDistro)
71+
.map((key: string) => ({
72+
data: [ratingDistro[key]],
73+
name: key.split('ratingRange')[1],
74+
type: 'column',
75+
}))
76+
77+
return options
78+
}, [memberStatsDist])
79+
80+
function toggleViewType(newViewType: SRMViewTypes): void {
81+
setviewType(newViewType)
82+
}
83+
84+
return (
85+
<BaseModal
86+
onClose={props.onClose}
87+
open={props.isAssemblyDetailsOpen}
88+
size='body'
89+
title='ASSEMBLY COMPETITION'
90+
>
91+
<LoadingSpinner hide={!!statsHistory && !!memberStatsDist} />
92+
93+
{!!statsHistory && !!memberStatsDist && (
94+
<div className={styles.container}>
95+
<div className='member-stat-header'>
96+
<div>
97+
<span
98+
className='member-stat-value'
99+
style={ratingToCSScolor(props.assemblyStats?.rank.rating || 0)}
100+
>
101+
{props.assemblyStats?.rank.rating}
102+
</span>
103+
Rating
104+
</div>
105+
<div>
106+
<span className='member-stat-value'>{props.assemblyStats?.rank.overallRank}</span>
107+
Rank
108+
</div>
109+
<div>
110+
<span className='member-stat-value'>
111+
{numberToFixed(props.assemblyStats?.rank.overallPercentile || 0)}
112+
%
113+
</span>
114+
Percentile
115+
</div>
116+
<div>
117+
<span className='member-stat-value'>{props.assemblyStats?.wins}</span>
118+
Wins
119+
</div>
120+
<div>
121+
<span className='member-stat-value'>{props.assemblyStats?.challenges}</span>
122+
Challenges
123+
</div>
124+
</div>
125+
126+
<div className={styles.content}>
127+
<div className={styles.contentHeader}>
128+
<h4>{viewType}</h4>
129+
<div className={styles.contentHeaderActions}>
130+
<Button
131+
primary
132+
onClick={bind(
133+
toggleViewType,
134+
this,
135+
viewType !== 'CHALLENGES DETAILS' ? 'CHALLENGES DETAILS' : 'STATISTICS',
136+
)}
137+
>
138+
See
139+
{' '}
140+
{viewType !== 'CHALLENGES DETAILS' ? 'CHALLENGES DETAILS' : 'STATISTICS'}
141+
</Button>
142+
</div>
143+
</div>
144+
145+
<div className={styles.contentBody}>
146+
{
147+
viewType === 'STATISTICS' && (
148+
<div>
149+
{
150+
ratingHistoryOptions && (
151+
<HighchartsReact
152+
highcharts={Highcharts}
153+
options={ratingHistoryOptions}
154+
/>
155+
)
156+
}
157+
{
158+
ratingDistributionOptions && (
159+
<HighchartsReact
160+
highcharts={Highcharts}
161+
options={ratingDistributionOptions}
162+
/>
163+
)
164+
}
165+
</div>
166+
)
167+
168+
}
169+
{
170+
viewType === 'CHALLENGES DETAILS' && (
171+
<div />
172+
)
173+
174+
}
175+
</div>
176+
</div>
177+
</div>
178+
)}
179+
</BaseModal>
180+
)
181+
}
182+
183+
export default AssemblyDetailsModal
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
export const RATING_CHART_CONFIG: Highcharts.Options = {
2+
credits: {
3+
enabled: false,
4+
},
5+
series: [{
6+
name: 'SRM Rating',
7+
type: 'spline',
8+
}],
9+
title: {
10+
text: 'RATING HISTORY',
11+
},
12+
tooltip: {
13+
pointFormat: '{point.x:%Y-%m-%d}: {point.y:.0f}',
14+
},
15+
xAxis: {
16+
labels: {
17+
format: '{value:%Y-%m-%d}',
18+
},
19+
type: 'datetime',
20+
},
21+
yAxis: {
22+
title: {
23+
text: 'Rating',
24+
},
25+
},
26+
}
27+
28+
export const RATING_DISTRO_CHART_CONFIG: Highcharts.Options = {
29+
chart: {
30+
type: 'column',
31+
},
32+
credits: {
33+
enabled: false,
34+
},
35+
legend: {
36+
enabled: false,
37+
},
38+
title: {
39+
text: 'RATING DISTRIBUTION',
40+
},
41+
tooltip: {
42+
pointFormat: '{series.name:.0f}: {point.y:.0f} Coders',
43+
},
44+
xAxis: {
45+
visible: false,
46+
},
47+
yAxis: {
48+
title: {
49+
text: 'Rating',
50+
},
51+
},
52+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as AssemblyDetailsModal } from './AssemblyDetailsModal'

0 commit comments

Comments
 (0)