@karimov-labs/backstage-plugin-devxp 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,340 @@
1
+ import React__default, { useState, useEffect, useCallback } from 'react';
2
+ import { Box, TextField, IconButton, Chip, FormControl, InputLabel, Select, MenuItem, CircularProgress, TableContainer, Paper, Table, TableHead, TableRow, TableCell, TableBody, Typography, LinearProgress, Tooltip, Button } from '@material-ui/core';
3
+ import Autocomplete from '@material-ui/lab/Autocomplete';
4
+ import { makeStyles } from '@material-ui/core/styles';
5
+ import SearchIcon from '@material-ui/icons/Search';
6
+ import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward';
7
+ import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward';
8
+ import CompareArrowsIcon from '@material-ui/icons/CompareArrows';
9
+ import PersonIcon from '@material-ui/icons/Person';
10
+ import { InfoCard, EmptyState } from '@backstage/core-components';
11
+
12
+ const CATEGORIES = [
13
+ "Frontend Engineering",
14
+ "Backend Systems",
15
+ "Database & Data",
16
+ "Infrastructure (DevOps)",
17
+ "Mobile Development",
18
+ "AI & Machine Learning",
19
+ "Testing & QA",
20
+ "Security & Auth",
21
+ "Distributed Systems",
22
+ "Documentation"
23
+ ];
24
+ const CATEGORY_COLORS = {
25
+ "Frontend Engineering": "#e91e63",
26
+ "Backend Systems": "#3f51b5",
27
+ "Database & Data": "#9c27b0",
28
+ "Infrastructure (DevOps)": "#ff9800",
29
+ "Mobile Development": "#4caf50",
30
+ "AI & Machine Learning": "#607d8b",
31
+ "Testing & QA": "#00bcd4",
32
+ "Security & Auth": "#f44336",
33
+ "Distributed Systems": "#795548",
34
+ "Documentation": "#9e9e9e"
35
+ };
36
+ const useStyles = makeStyles((theme) => ({
37
+ filters: {
38
+ display: "flex",
39
+ flexWrap: "wrap",
40
+ gap: theme.spacing(2),
41
+ marginBottom: theme.spacing(2),
42
+ alignItems: "flex-end"
43
+ },
44
+ searchField: {
45
+ minWidth: 200,
46
+ flex: 1
47
+ },
48
+ repoField: {
49
+ minWidth: 240,
50
+ flex: 2
51
+ },
52
+ selectField: {
53
+ minWidth: 160
54
+ },
55
+ categorySelect: {
56
+ minWidth: 200,
57
+ flex: 1
58
+ },
59
+ rankCell: {
60
+ fontWeight: 700,
61
+ color: theme.palette.text.secondary,
62
+ width: 48
63
+ },
64
+ userIdCell: {
65
+ fontFamily: "monospace",
66
+ fontWeight: 600
67
+ },
68
+ scoreBar: {
69
+ height: 6,
70
+ borderRadius: 3,
71
+ marginTop: 4
72
+ },
73
+ categoryChip: {
74
+ margin: theme.spacing(0.25),
75
+ fontSize: "0.65rem",
76
+ height: 18
77
+ },
78
+ clickableRow: {
79
+ cursor: "pointer",
80
+ "&:hover": {
81
+ backgroundColor: theme.palette.action.hover
82
+ }
83
+ },
84
+ pagination: {
85
+ display: "flex",
86
+ justifyContent: "space-between",
87
+ alignItems: "center",
88
+ padding: theme.spacing(1, 0),
89
+ marginTop: theme.spacing(1)
90
+ },
91
+ medalGold: { color: "#FFD700", fontWeight: 700 },
92
+ medalSilver: { color: "#C0C0C0", fontWeight: 700 },
93
+ medalBronze: { color: "#CD7F32", fontWeight: 700 },
94
+ actionButtons: {
95
+ display: "flex",
96
+ gap: theme.spacing(0.5)
97
+ }
98
+ }));
99
+ const DeveloperLeaderboard = ({
100
+ api,
101
+ onViewDeveloper,
102
+ onCompareDeveloper,
103
+ compareList,
104
+ repoOptions: repoOptionsProp
105
+ }) => {
106
+ const classes = useStyles();
107
+ const [developers, setDevelopers] = useState([]);
108
+ const [totalCount, setTotalCount] = useState(0);
109
+ const [totalPages, setTotalPages] = useState(1);
110
+ const [loading, setLoading] = useState(true);
111
+ const [error, setError] = useState(null);
112
+ const [ownRepoOptions, setOwnRepoOptions] = useState([]);
113
+ const repoOptions = repoOptionsProp ?? ownRepoOptions;
114
+ const [filters, setFilters] = useState({
115
+ page: 1,
116
+ pageSize: 20,
117
+ categories: [],
118
+ repoNames: [],
119
+ sortBy: "avgProficiency",
120
+ sortOrder: "desc",
121
+ searchQuery: ""
122
+ });
123
+ const [searchInput, setSearchInput] = useState("");
124
+ useEffect(() => {
125
+ if (repoOptionsProp !== void 0) return;
126
+ api.getRepositories({ pageSize: 200 }).then((data) => setOwnRepoOptions(data.repositories.map((r) => r.repoName))).catch(() => {
127
+ });
128
+ }, [api, repoOptionsProp]);
129
+ const load = useCallback(
130
+ async (f) => {
131
+ setLoading(true);
132
+ setError(null);
133
+ try {
134
+ const data = await api.getDeveloperLeaderboard(f);
135
+ setDevelopers(data.developers);
136
+ setTotalCount(data.totalCount);
137
+ setTotalPages(data.totalPages);
138
+ } catch (e) {
139
+ setError(e.message ?? "Failed to load leaderboard");
140
+ } finally {
141
+ setLoading(false);
142
+ }
143
+ },
144
+ [api]
145
+ );
146
+ useEffect(() => {
147
+ load(filters);
148
+ }, [load, filters]);
149
+ const applySearch = () => {
150
+ setFilters((f) => ({ ...f, page: 1, searchQuery: searchInput }));
151
+ };
152
+ const handleKeyDown = (e) => {
153
+ if (e.key === "Enter") applySearch();
154
+ };
155
+ const toggleSort = (field) => {
156
+ setFilters((f) => ({
157
+ ...f,
158
+ page: 1,
159
+ sortBy: field,
160
+ sortOrder: f.sortBy === field && f.sortOrder === "desc" ? "asc" : "desc"
161
+ }));
162
+ };
163
+ const SortIcon = ({ field }) => {
164
+ if (filters.sortBy !== field) return null;
165
+ return filters.sortOrder === "desc" ? /* @__PURE__ */ React__default.createElement(ArrowDownwardIcon, { style: { fontSize: 14, verticalAlign: "middle" } }) : /* @__PURE__ */ React__default.createElement(ArrowUpwardIcon, { style: { fontSize: 14, verticalAlign: "middle" } });
166
+ };
167
+ const rankLabel = (rank) => {
168
+ if (rank === 1) return /* @__PURE__ */ React__default.createElement("span", { className: classes.medalGold }, "\u{1F947} 1");
169
+ if (rank === 2) return /* @__PURE__ */ React__default.createElement("span", { className: classes.medalSilver }, "\u{1F948} 2");
170
+ if (rank === 3) return /* @__PURE__ */ React__default.createElement("span", { className: classes.medalBronze }, "\u{1F949} 3");
171
+ return /* @__PURE__ */ React__default.createElement("span", null, rank);
172
+ };
173
+ const globalRank = (idx) => (filters.page - 1) * filters.pageSize + idx + 1;
174
+ return /* @__PURE__ */ React__default.createElement(InfoCard, { title: `Developer Leaderboard (${totalCount})`, noPadding: false }, /* @__PURE__ */ React__default.createElement(Box, { className: classes.filters }, /* @__PURE__ */ React__default.createElement(
175
+ TextField,
176
+ {
177
+ className: classes.searchField,
178
+ size: "small",
179
+ label: "Search developer",
180
+ variant: "outlined",
181
+ value: searchInput,
182
+ onChange: (e) => setSearchInput(e.target.value),
183
+ onKeyDown: handleKeyDown,
184
+ InputProps: {
185
+ endAdornment: /* @__PURE__ */ React__default.createElement(IconButton, { size: "small", onClick: applySearch }, /* @__PURE__ */ React__default.createElement(SearchIcon, { fontSize: "small" }))
186
+ }
187
+ }
188
+ ), /* @__PURE__ */ React__default.createElement(
189
+ Autocomplete,
190
+ {
191
+ className: classes.repoField,
192
+ multiple: true,
193
+ options: repoOptions,
194
+ value: filters.repoNames,
195
+ onChange: (_e, value) => setFilters((f) => ({ ...f, page: 1, repoNames: value })),
196
+ renderTags: (value, getTagProps) => value.map((option, index) => /* @__PURE__ */ React__default.createElement(
197
+ Chip,
198
+ {
199
+ key: option,
200
+ label: option.split("/").pop() ?? option,
201
+ size: "small",
202
+ ...getTagProps({ index })
203
+ }
204
+ )),
205
+ renderInput: (params) => /* @__PURE__ */ React__default.createElement(
206
+ TextField,
207
+ {
208
+ ...params,
209
+ variant: "outlined",
210
+ size: "small",
211
+ label: "Repositories",
212
+ placeholder: filters.repoNames.length === 0 ? "Filter by repo\u2026" : ""
213
+ }
214
+ )
215
+ }
216
+ ), /* @__PURE__ */ React__default.createElement(FormControl, { variant: "outlined", size: "small", className: classes.categorySelect }, /* @__PURE__ */ React__default.createElement(InputLabel, null, "Expertise domain"), /* @__PURE__ */ React__default.createElement(
217
+ Select,
218
+ {
219
+ multiple: true,
220
+ value: filters.categories,
221
+ onChange: (e) => setFilters((f) => ({ ...f, page: 1, categories: e.target.value })),
222
+ label: "Expertise domain",
223
+ renderValue: (selected) => selected.map((s) => s.split(" ")[0]).join(", ")
224
+ },
225
+ CATEGORIES.map((cat) => /* @__PURE__ */ React__default.createElement(MenuItem, { key: cat, value: cat }, cat))
226
+ )), /* @__PURE__ */ React__default.createElement(FormControl, { variant: "outlined", size: "small", className: classes.selectField }, /* @__PURE__ */ React__default.createElement(InputLabel, null, "Sort by"), /* @__PURE__ */ React__default.createElement(
227
+ Select,
228
+ {
229
+ value: filters.sortBy,
230
+ onChange: (e) => setFilters((f) => ({ ...f, page: 1, sortBy: e.target.value })),
231
+ label: "Sort by"
232
+ },
233
+ /* @__PURE__ */ React__default.createElement(MenuItem, { value: "avgProficiency" }, "Avg Proficiency"),
234
+ /* @__PURE__ */ React__default.createElement(MenuItem, { value: "domainSkillsScore" }, "Domain Score")
235
+ ))), loading ? /* @__PURE__ */ React__default.createElement(Box, { display: "flex", justifyContent: "center", py: 4 }, /* @__PURE__ */ React__default.createElement(CircularProgress, null)) : error ? /* @__PURE__ */ React__default.createElement(
236
+ EmptyState,
237
+ {
238
+ missing: "data",
239
+ title: "Leaderboard unavailable",
240
+ description: error
241
+ }
242
+ ) : developers.length === 0 ? /* @__PURE__ */ React__default.createElement(
243
+ EmptyState,
244
+ {
245
+ missing: "data",
246
+ title: "No developers found",
247
+ description: "Try adjusting the filters."
248
+ }
249
+ ) : /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(TableContainer, { component: Paper, variant: "outlined" }, /* @__PURE__ */ React__default.createElement(Table, { size: "small" }, /* @__PURE__ */ React__default.createElement(TableHead, null, /* @__PURE__ */ React__default.createElement(TableRow, null, /* @__PURE__ */ React__default.createElement(TableCell, { className: classes.rankCell }, "#"), /* @__PURE__ */ React__default.createElement(TableCell, null, "Developer"), /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(
250
+ "span",
251
+ {
252
+ style: { cursor: "pointer" },
253
+ onClick: () => toggleSort("avgProficiency")
254
+ },
255
+ "Avg Proficiency ",
256
+ /* @__PURE__ */ React__default.createElement(SortIcon, { field: "avgProficiency" })
257
+ )), /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(
258
+ "span",
259
+ {
260
+ style: { cursor: "pointer" },
261
+ onClick: () => toggleSort("domainSkillsScore")
262
+ },
263
+ "Domain Score ",
264
+ /* @__PURE__ */ React__default.createElement(SortIcon, { field: "domainSkillsScore" })
265
+ )), /* @__PURE__ */ React__default.createElement(TableCell, null, "Skills"), /* @__PURE__ */ React__default.createElement(TableCell, null, "Top Domains"), /* @__PURE__ */ React__default.createElement(TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ React__default.createElement(TableBody, null, developers.map((dev, idx) => /* @__PURE__ */ React__default.createElement(
266
+ TableRow,
267
+ {
268
+ key: dev.userId,
269
+ className: classes.clickableRow,
270
+ onClick: () => onViewDeveloper(dev.userId)
271
+ },
272
+ /* @__PURE__ */ React__default.createElement(TableCell, { className: classes.rankCell }, rankLabel(globalRank(idx))),
273
+ /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2", className: classes.userIdCell }, dev.userId)),
274
+ /* @__PURE__ */ React__default.createElement(TableCell, { style: { minWidth: 120 } }, /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2", style: { fontWeight: 600 } }, (dev.averageProficiency * 10).toFixed(1), "%"), /* @__PURE__ */ React__default.createElement(
275
+ LinearProgress,
276
+ {
277
+ variant: "determinate",
278
+ value: dev.averageProficiency * 10,
279
+ className: classes.scoreBar,
280
+ style: { color: "#3f51b5" }
281
+ }
282
+ )),
283
+ /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2" }, dev.sumOfAveragedSkillsProficiency.toFixed(2))),
284
+ /* @__PURE__ */ React__default.createElement(TableCell, null, /* @__PURE__ */ React__default.createElement(Typography, { variant: "body2" }, dev.skillCount)),
285
+ /* @__PURE__ */ React__default.createElement(TableCell, { style: { maxWidth: 200 } }, dev.categoryBreakdown.sort((a, b) => b.averageProficiency - a.averageProficiency).slice(0, 3).map((cat) => /* @__PURE__ */ React__default.createElement(
286
+ Chip,
287
+ {
288
+ key: cat.category,
289
+ label: cat.category.split(" ")[0],
290
+ size: "small",
291
+ className: classes.categoryChip,
292
+ style: {
293
+ backgroundColor: `${CATEGORY_COLORS[cat.category] ?? "#9e9e9e"}20`,
294
+ color: CATEGORY_COLORS[cat.category] ?? "#9e9e9e"
295
+ }
296
+ }
297
+ ))),
298
+ /* @__PURE__ */ React__default.createElement(TableCell, { align: "right", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ React__default.createElement(Box, { className: classes.actionButtons }, /* @__PURE__ */ React__default.createElement(Tooltip, { title: "View details" }, /* @__PURE__ */ React__default.createElement(
299
+ IconButton,
300
+ {
301
+ size: "small",
302
+ onClick: () => onViewDeveloper(dev.userId)
303
+ },
304
+ /* @__PURE__ */ React__default.createElement(PersonIcon, { fontSize: "small" })
305
+ )), /* @__PURE__ */ React__default.createElement(
306
+ Tooltip,
307
+ {
308
+ title: compareList.includes(dev.userId) ? "Added to comparison" : "Add to comparison"
309
+ },
310
+ /* @__PURE__ */ React__default.createElement(
311
+ IconButton,
312
+ {
313
+ size: "small",
314
+ color: compareList.includes(dev.userId) ? "primary" : "default",
315
+ onClick: () => onCompareDeveloper(dev.userId)
316
+ },
317
+ /* @__PURE__ */ React__default.createElement(CompareArrowsIcon, { fontSize: "small" })
318
+ )
319
+ )))
320
+ ))))), /* @__PURE__ */ React__default.createElement(Box, { className: classes.pagination }, /* @__PURE__ */ React__default.createElement(Typography, { variant: "caption", color: "textSecondary" }, "Showing ", (filters.page - 1) * filters.pageSize + 1, "\u2013", Math.min(filters.page * filters.pageSize, totalCount), " of ", totalCount), /* @__PURE__ */ React__default.createElement(Box, { display: "flex", alignItems: "center", style: { gap: 8 } }, /* @__PURE__ */ React__default.createElement(
321
+ Button,
322
+ {
323
+ size: "small",
324
+ disabled: filters.page <= 1,
325
+ onClick: () => setFilters((f) => ({ ...f, page: f.page - 1 }))
326
+ },
327
+ "Prev"
328
+ ), /* @__PURE__ */ React__default.createElement(Typography, { variant: "caption" }, filters.page, " / ", totalPages), /* @__PURE__ */ React__default.createElement(
329
+ Button,
330
+ {
331
+ size: "small",
332
+ disabled: filters.page >= totalPages,
333
+ onClick: () => setFilters((f) => ({ ...f, page: f.page + 1 }))
334
+ },
335
+ "Next"
336
+ )))));
337
+ };
338
+
339
+ export { DeveloperLeaderboard };
340
+ //# sourceMappingURL=DeveloperLeaderboard.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeveloperLeaderboard.esm.js","sources":["../../src/components/DeveloperLeaderboard.tsx"],"sourcesContent":["import React, { useState, useCallback, useEffect } from 'react';\nimport {\n Box,\n Typography,\n Table,\n TableBody,\n TableCell,\n TableContainer,\n TableHead,\n TableRow,\n Paper,\n TextField,\n Select,\n MenuItem,\n FormControl,\n InputLabel,\n Chip,\n IconButton,\n Button,\n CircularProgress,\n LinearProgress,\n Tooltip,\n} from '@material-ui/core';\nimport Autocomplete from '@material-ui/lab/Autocomplete';\nimport { makeStyles } from '@material-ui/core/styles';\nimport SearchIcon from '@material-ui/icons/Search';\nimport ArrowUpwardIcon from '@material-ui/icons/ArrowUpward';\nimport ArrowDownwardIcon from '@material-ui/icons/ArrowDownward';\nimport CompareArrowsIcon from '@material-ui/icons/CompareArrows';\nimport PersonIcon from '@material-ui/icons/Person';\nimport { InfoCard, EmptyState } from '@backstage/core-components';\nimport type { DevxpApi } from '../api';\nimport type { LeaderboardDeveloper, LeaderboardFilters } from '../types';\n\nconst CATEGORIES = [\n 'Frontend Engineering',\n 'Backend Systems',\n 'Database & Data',\n 'Infrastructure (DevOps)',\n 'Mobile Development',\n 'AI & Machine Learning',\n 'Testing & QA',\n 'Security & Auth',\n 'Distributed Systems',\n 'Documentation',\n];\n\nconst CATEGORY_COLORS: Record<string, string> = {\n 'Frontend Engineering': '#e91e63',\n 'Backend Systems': '#3f51b5',\n 'Database & Data': '#9c27b0',\n 'Infrastructure (DevOps)': '#ff9800',\n 'Mobile Development': '#4caf50',\n 'AI & Machine Learning': '#607d8b',\n 'Testing & QA': '#00bcd4',\n 'Security & Auth': '#f44336',\n 'Distributed Systems': '#795548',\n 'Documentation': '#9e9e9e',\n};\n\nconst useStyles = makeStyles(theme => ({\n filters: {\n display: 'flex',\n flexWrap: 'wrap',\n gap: theme.spacing(2),\n marginBottom: theme.spacing(2),\n alignItems: 'flex-end',\n },\n searchField: {\n minWidth: 200,\n flex: 1,\n },\n repoField: {\n minWidth: 240,\n flex: 2,\n },\n selectField: {\n minWidth: 160,\n },\n categorySelect: {\n minWidth: 200,\n flex: 1,\n },\n rankCell: {\n fontWeight: 700,\n color: theme.palette.text.secondary,\n width: 48,\n },\n userIdCell: {\n fontFamily: 'monospace',\n fontWeight: 600,\n },\n scoreBar: {\n height: 6,\n borderRadius: 3,\n marginTop: 4,\n },\n categoryChip: {\n margin: theme.spacing(0.25),\n fontSize: '0.65rem',\n height: 18,\n },\n clickableRow: {\n cursor: 'pointer',\n '&:hover': {\n backgroundColor: theme.palette.action.hover,\n },\n },\n pagination: {\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n padding: theme.spacing(1, 0),\n marginTop: theme.spacing(1),\n },\n medalGold: { color: '#FFD700', fontWeight: 700 },\n medalSilver: { color: '#C0C0C0', fontWeight: 700 },\n medalBronze: { color: '#CD7F32', fontWeight: 700 },\n actionButtons: {\n display: 'flex',\n gap: theme.spacing(0.5),\n },\n}));\n\ninterface DeveloperLeaderboardProps {\n api: DevxpApi;\n onViewDeveloper: (userId: string) => void;\n onCompareDeveloper: (userId: string) => void;\n compareList: string[];\n /** Pre-loaded repo names from the parent. When provided the component skips its own fetch. */\n repoOptions?: string[];\n}\n\nexport const DeveloperLeaderboard = ({\n api,\n onViewDeveloper,\n onCompareDeveloper,\n compareList,\n repoOptions: repoOptionsProp,\n}: DeveloperLeaderboardProps) => {\n const classes = useStyles();\n\n const [developers, setDevelopers] = useState<LeaderboardDeveloper[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [totalPages, setTotalPages] = useState(1);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n // Use parent-provided options when available, otherwise fetch own\n const [ownRepoOptions, setOwnRepoOptions] = useState<string[]>([]);\n const repoOptions = repoOptionsProp ?? ownRepoOptions;\n\n const [filters, setFilters] = useState<LeaderboardFilters>({\n page: 1,\n pageSize: 20,\n categories: [],\n repoNames: [],\n sortBy: 'avgProficiency',\n sortOrder: 'desc',\n searchQuery: '',\n });\n const [searchInput, setSearchInput] = useState('');\n\n // Only fetch repo names ourselves if the parent didn't supply them\n useEffect(() => {\n if (repoOptionsProp !== undefined) return;\n api\n .getRepositories({ pageSize: 200 })\n .then(data => setOwnRepoOptions(data.repositories.map(r => r.repoName)))\n .catch(() => {});\n }, [api, repoOptionsProp]);\n\n const load = useCallback(\n async (f: LeaderboardFilters) => {\n setLoading(true);\n setError(null);\n try {\n const data = await api.getDeveloperLeaderboard(f);\n setDevelopers(data.developers);\n setTotalCount(data.totalCount);\n setTotalPages(data.totalPages);\n } catch (e: any) {\n setError(e.message ?? 'Failed to load leaderboard');\n } finally {\n setLoading(false);\n }\n },\n [api],\n );\n\n useEffect(() => {\n load(filters);\n }, [load, filters]);\n\n const applySearch = () => {\n setFilters(f => ({ ...f, page: 1, searchQuery: searchInput }));\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') applySearch();\n };\n\n const toggleSort = (field: 'avgProficiency' | 'domainSkillsScore') => {\n setFilters(f => ({\n ...f,\n page: 1,\n sortBy: field,\n sortOrder: f.sortBy === field && f.sortOrder === 'desc' ? 'asc' : 'desc',\n }));\n };\n\n const SortIcon = ({ field }: { field: 'avgProficiency' | 'domainSkillsScore' }) => {\n if (filters.sortBy !== field) return null;\n return filters.sortOrder === 'desc' ? (\n <ArrowDownwardIcon style={{ fontSize: 14, verticalAlign: 'middle' }} />\n ) : (\n <ArrowUpwardIcon style={{ fontSize: 14, verticalAlign: 'middle' }} />\n );\n };\n\n const rankLabel = (rank: number) => {\n if (rank === 1) return <span className={classes.medalGold}>🥇 1</span>;\n if (rank === 2) return <span className={classes.medalSilver}>🥈 2</span>;\n if (rank === 3) return <span className={classes.medalBronze}>🥉 3</span>;\n return <span>{rank}</span>;\n };\n\n const globalRank = (idx: number) => (filters.page - 1) * filters.pageSize + idx + 1;\n\n return (\n <InfoCard title={`Developer Leaderboard (${totalCount})`} noPadding={false}>\n {/* Filters */}\n <Box className={classes.filters}>\n <TextField\n className={classes.searchField}\n size=\"small\"\n label=\"Search developer\"\n variant=\"outlined\"\n value={searchInput}\n onChange={e => setSearchInput(e.target.value)}\n onKeyDown={handleKeyDown}\n InputProps={{\n endAdornment: (\n <IconButton size=\"small\" onClick={applySearch}>\n <SearchIcon fontSize=\"small\" />\n </IconButton>\n ),\n }}\n />\n <Autocomplete\n className={classes.repoField}\n multiple\n options={repoOptions}\n value={filters.repoNames}\n onChange={(_e, value) =>\n setFilters(f => ({ ...f, page: 1, repoNames: value }))\n }\n renderTags={(value, getTagProps) =>\n value.map((option, index) => (\n <Chip\n key={option}\n label={option.split('/').pop() ?? option}\n size=\"small\"\n {...getTagProps({ index })}\n />\n ))\n }\n renderInput={params => (\n <TextField\n {...params}\n variant=\"outlined\"\n size=\"small\"\n label=\"Repositories\"\n placeholder={filters.repoNames.length === 0 ? 'Filter by repo…' : ''}\n />\n )}\n />\n <FormControl variant=\"outlined\" size=\"small\" className={classes.categorySelect}>\n <InputLabel>Expertise domain</InputLabel>\n <Select\n multiple\n value={filters.categories}\n onChange={e =>\n setFilters(f => ({ ...f, page: 1, categories: e.target.value as string[] }))\n }\n label=\"Expertise domain\"\n renderValue={(selected: any) =>\n (selected as string[])\n .map(s => s.split(' ')[0])\n .join(', ')\n }\n >\n {CATEGORIES.map(cat => (\n <MenuItem key={cat} value={cat}>\n {cat}\n </MenuItem>\n ))}\n </Select>\n </FormControl>\n <FormControl variant=\"outlined\" size=\"small\" className={classes.selectField}>\n <InputLabel>Sort by</InputLabel>\n <Select\n value={filters.sortBy}\n onChange={e =>\n setFilters(f => ({ ...f, page: 1, sortBy: e.target.value as any }))\n }\n label=\"Sort by\"\n >\n <MenuItem value=\"avgProficiency\">Avg Proficiency</MenuItem>\n <MenuItem value=\"domainSkillsScore\">Domain Score</MenuItem>\n </Select>\n </FormControl>\n </Box>\n\n {loading ? (\n <Box display=\"flex\" justifyContent=\"center\" py={4}>\n <CircularProgress />\n </Box>\n ) : error ? (\n <EmptyState\n missing=\"data\"\n title=\"Leaderboard unavailable\"\n description={error}\n />\n ) : developers.length === 0 ? (\n <EmptyState\n missing=\"data\"\n title=\"No developers found\"\n description=\"Try adjusting the filters.\"\n />\n ) : (\n <React.Fragment>\n <TableContainer component={Paper} variant=\"outlined\">\n <Table size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell className={classes.rankCell}>#</TableCell>\n <TableCell>Developer</TableCell>\n <TableCell>\n <span\n style={{ cursor: 'pointer' }}\n onClick={() => toggleSort('avgProficiency')}\n >\n Avg Proficiency <SortIcon field=\"avgProficiency\" />\n </span>\n </TableCell>\n <TableCell>\n <span\n style={{ cursor: 'pointer' }}\n onClick={() => toggleSort('domainSkillsScore')}\n >\n Domain Score <SortIcon field=\"domainSkillsScore\" />\n </span>\n </TableCell>\n <TableCell>Skills</TableCell>\n <TableCell>Top Domains</TableCell>\n <TableCell align=\"right\">Actions</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {developers.map((dev, idx) => (\n <TableRow\n key={dev.userId}\n className={classes.clickableRow}\n onClick={() => onViewDeveloper(dev.userId)}\n >\n <TableCell className={classes.rankCell}>\n {rankLabel(globalRank(idx))}\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\" className={classes.userIdCell}>\n {dev.userId}\n </Typography>\n </TableCell>\n <TableCell style={{ minWidth: 120 }}>\n <Typography variant=\"body2\" style={{ fontWeight: 600 }}>\n {(dev.averageProficiency * 10).toFixed(1)}%\n </Typography>\n <LinearProgress\n variant=\"determinate\"\n value={dev.averageProficiency * 10}\n className={classes.scoreBar}\n style={{ color: '#3f51b5' }}\n />\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\">\n {dev.sumOfAveragedSkillsProficiency.toFixed(2)}\n </Typography>\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\">{dev.skillCount}</Typography>\n </TableCell>\n <TableCell style={{ maxWidth: 200 }}>\n {dev.categoryBreakdown\n .sort((a, b) => b.averageProficiency - a.averageProficiency)\n .slice(0, 3)\n .map(cat => (\n <Chip\n key={cat.category}\n label={cat.category.split(' ')[0]}\n size=\"small\"\n className={classes.categoryChip}\n style={{\n backgroundColor: `${CATEGORY_COLORS[cat.category] ?? '#9e9e9e'}20`,\n color: CATEGORY_COLORS[cat.category] ?? '#9e9e9e',\n }}\n />\n ))}\n </TableCell>\n <TableCell align=\"right\" onClick={e => e.stopPropagation()}>\n <Box className={classes.actionButtons}>\n <Tooltip title=\"View details\">\n <IconButton\n size=\"small\"\n onClick={() => onViewDeveloper(dev.userId)}\n >\n <PersonIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n <Tooltip\n title={\n compareList.includes(dev.userId)\n ? 'Added to comparison'\n : 'Add to comparison'\n }\n >\n <IconButton\n size=\"small\"\n color={compareList.includes(dev.userId) ? 'primary' : 'default'}\n onClick={() => onCompareDeveloper(dev.userId)}\n >\n <CompareArrowsIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n </Box>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </TableContainer>\n\n {/* Pagination */}\n <Box className={classes.pagination}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n Showing {(filters.page - 1) * filters.pageSize + 1}–\n {Math.min(filters.page * filters.pageSize, totalCount)} of {totalCount}\n </Typography>\n <Box display=\"flex\" alignItems=\"center\" style={{ gap: 8 }}>\n <Button\n size=\"small\"\n disabled={filters.page <= 1}\n onClick={() => setFilters(f => ({ ...f, page: f.page - 1 }))}\n >\n Prev\n </Button>\n <Typography variant=\"caption\">\n {filters.page} / {totalPages}\n </Typography>\n <Button\n size=\"small\"\n disabled={filters.page >= totalPages}\n onClick={() => setFilters(f => ({ ...f, page: f.page + 1 }))}\n >\n Next\n </Button>\n </Box>\n </Box>\n </React.Fragment>\n )}\n </InfoCard>\n );\n};\n"],"names":["React"],"mappings":"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"}
@@ -1,10 +1,16 @@
1
1
  import * as React from 'react';
2
2
  import { Page, Header, Content } from '@backstage/core-components';
3
3
  import { useApi, fetchApiRef, discoveryApiRef } from '@backstage/core-plugin-api';
4
- import { Tabs, Tab, Box } from '@material-ui/core';
4
+ import { Tabs, Tab, Box, Button } from '@material-ui/core';
5
+ import CompareArrowsIcon from '@material-ui/icons/CompareArrows';
5
6
  import { DevxpClient } from '../api.esm.js';
7
+ import { AnalyticsDashboard } from './AnalyticsDashboard.esm.js';
6
8
  import { DashboardContent } from './DashboardContent.esm.js';
7
9
  import { SettingsContent } from './SettingsContent.esm.js';
10
+ import { DeveloperLeaderboard } from './DeveloperLeaderboard.esm.js';
11
+ import { DeveloperDetails } from './DeveloperDetails.esm.js';
12
+ import { DeveloperComparison } from './DeveloperComparison.esm.js';
13
+ import { RepositoriesContent } from './RepositoriesContent.esm.js';
8
14
 
9
15
  const DevxpPage = () => {
10
16
  const [tab, setTab] = React.useState(0);
@@ -14,6 +20,29 @@ const DevxpPage = () => {
14
20
  () => new DevxpClient({ fetchApi, discoveryApi }),
15
21
  [fetchApi, discoveryApi]
16
22
  );
23
+ const [repoOptions, setRepoOptions] = React.useState([]);
24
+ React.useEffect(() => {
25
+ api.getRepositories({ pageSize: 200 }).then((data) => setRepoOptions(data.repositories.map((r) => r.repoName))).catch(() => {
26
+ });
27
+ }, [api]);
28
+ const [devView, setDevView] = React.useState("leaderboard");
29
+ const [selectedUserId, setSelectedUserId] = React.useState("");
30
+ const [compareList, setCompareList] = React.useState([]);
31
+ const handleViewDeveloper = (userId) => {
32
+ setSelectedUserId(userId);
33
+ setDevView("details");
34
+ };
35
+ const handleToggleCompare = (userId) => {
36
+ setCompareList(
37
+ (prev) => prev.includes(userId) ? prev.filter((id) => id !== userId) : [...prev.slice(-1), userId]
38
+ );
39
+ };
40
+ const handleOpenComparison = () => {
41
+ setDevView("comparison");
42
+ };
43
+ const handleBackToLeaderboard = () => {
44
+ setDevView("leaderboard");
45
+ };
17
46
  return /* @__PURE__ */ React.createElement(Page, { themeId: "tool" }, /* @__PURE__ */ React.createElement(
18
47
  Header,
19
48
  {
@@ -28,9 +57,58 @@ const DevxpPage = () => {
28
57
  indicatorColor: "primary",
29
58
  textColor: "primary"
30
59
  },
31
- /* @__PURE__ */ React.createElement(Tab, { label: "Dashboard" }),
60
+ /* @__PURE__ */ React.createElement(Tab, { label: "Analytics" }),
61
+ /* @__PURE__ */ React.createElement(Tab, { label: "Developers" }),
62
+ /* @__PURE__ */ React.createElement(Tab, { label: "Repositories" }),
63
+ /* @__PURE__ */ React.createElement(Tab, { label: "Identity" }),
32
64
  /* @__PURE__ */ React.createElement(Tab, { label: "Settings" })
33
- ), /* @__PURE__ */ React.createElement(Box, { mt: 3 }, tab === 0 && /* @__PURE__ */ React.createElement(DashboardContent, { api }), tab === 1 && /* @__PURE__ */ React.createElement(SettingsContent, { api }))));
65
+ ), /* @__PURE__ */ React.createElement(Box, { mt: 3 }, tab === 0 && /* @__PURE__ */ React.createElement(AnalyticsDashboard, { api }), tab === 1 && /* @__PURE__ */ React.createElement(Box, null, devView === "leaderboard" && /* @__PURE__ */ React.createElement(React.Fragment, null, compareList.length > 0 && /* @__PURE__ */ React.createElement(Box, { mb: 2, display: "flex", justifyContent: "flex-end" }, /* @__PURE__ */ React.createElement(
66
+ Button,
67
+ {
68
+ variant: "outlined",
69
+ color: "primary",
70
+ size: "small",
71
+ startIcon: /* @__PURE__ */ React.createElement(CompareArrowsIcon, null),
72
+ onClick: handleOpenComparison
73
+ },
74
+ "Compare (",
75
+ compareList.length,
76
+ ")"
77
+ )), /* @__PURE__ */ React.createElement(
78
+ DeveloperLeaderboard,
79
+ {
80
+ api,
81
+ onViewDeveloper: handleViewDeveloper,
82
+ onCompareDeveloper: handleToggleCompare,
83
+ compareList,
84
+ repoOptions
85
+ }
86
+ )), devView === "details" && /* @__PURE__ */ React.createElement(
87
+ DeveloperDetails,
88
+ {
89
+ api,
90
+ userId: selectedUserId,
91
+ onBack: handleBackToLeaderboard,
92
+ onCompare: handleToggleCompare,
93
+ isInCompare: compareList.includes(selectedUserId)
94
+ }
95
+ ), devView === "comparison" && /* @__PURE__ */ React.createElement(
96
+ DeveloperComparison,
97
+ {
98
+ api,
99
+ initialUserIds: compareList,
100
+ onBack: handleBackToLeaderboard
101
+ }
102
+ )), tab === 2 && /* @__PURE__ */ React.createElement(
103
+ RepositoriesContent,
104
+ {
105
+ api,
106
+ onViewDeveloper: (userId) => {
107
+ handleViewDeveloper(userId);
108
+ setTab(1);
109
+ }
110
+ }
111
+ ), tab === 3 && /* @__PURE__ */ React.createElement(DashboardContent, { api }), tab === 4 && /* @__PURE__ */ React.createElement(SettingsContent, { api }))));
34
112
  };
35
113
 
36
114
  export { DevxpPage };
@@ -1 +1 @@
1
- {"version":3,"file":"DevxpPage.esm.js","sources":["../../src/components/DevxpPage.tsx"],"sourcesContent":["import * as React from 'react';\nimport { Header, Page, Content } from '@backstage/core-components';\nimport {\n useApi,\n fetchApiRef,\n discoveryApiRef,\n} from '@backstage/core-plugin-api';\nimport { Tabs, Tab, Box } from '@material-ui/core';\nimport { DevxpClient } from '../api';\nimport { DashboardContent } from './DashboardContent';\nimport { SettingsContent } from './SettingsContent';\n\nexport const DevxpPage = () => {\n const [tab, setTab] = React.useState(0);\n const fetchApi = useApi(fetchApiRef);\n const discoveryApi = useApi(discoveryApiRef);\n\n const api = React.useMemo(\n () => new DevxpClient({ fetchApi, discoveryApi }),\n [fetchApi, discoveryApi],\n );\n\n return (\n <Page themeId=\"tool\">\n <Header\n title=\"Developer Intelligence\"\n subtitle=\"DevXP Analytics & Developer Name Mapping\"\n />\n <Content>\n <Tabs\n value={tab}\n onChange={(_, v) => setTab(v)}\n indicatorColor=\"primary\"\n textColor=\"primary\"\n >\n <Tab label=\"Dashboard\" />\n <Tab label=\"Settings\" />\n </Tabs>\n <Box mt={3}>\n {tab === 0 && <DashboardContent api={api} />}\n {tab === 1 && <SettingsContent api={api} />}\n </Box>\n </Content>\n </Page>\n );\n};\n"],"names":[],"mappings":";;;;;;;;AAYO,MAAM,YAAY,MAAM;AAC7B,EAAA,MAAM,CAAC,GAAA,EAAK,MAAM,CAAA,GAAI,KAAA,CAAM,SAAS,CAAC,CAAA;AACtC,EAAA,MAAM,QAAA,GAAW,OAAO,WAAW,CAAA;AACnC,EAAA,MAAM,YAAA,GAAe,OAAO,eAAe,CAAA;AAE3C,EAAA,MAAM,MAAM,KAAA,CAAM,OAAA;AAAA,IAChB,MAAM,IAAI,WAAA,CAAY,EAAE,QAAA,EAAU,cAAc,CAAA;AAAA,IAChD,CAAC,UAAU,YAAY;AAAA,GACzB;AAEA,EAAA,uBACE,KAAA,CAAA,aAAA,CAAC,IAAA,EAAA,EAAK,OAAA,EAAQ,MAAA,EAAA,kBACZ,KAAA,CAAA,aAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAM,wBAAA;AAAA,MACN,QAAA,EAAS;AAAA;AAAA,GACX,sCACC,OAAA,EAAA,IAAA,kBACC,KAAA,CAAA,aAAA;AAAA,IAAC,IAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,GAAA;AAAA,MACP,QAAA,EAAU,CAAC,CAAA,EAAG,CAAA,KAAM,OAAO,CAAC,CAAA;AAAA,MAC5B,cAAA,EAAe,SAAA;AAAA,MACf,SAAA,EAAU;AAAA,KAAA;AAAA,oBAEV,KAAA,CAAA,aAAA,CAAC,GAAA,EAAA,EAAI,KAAA,EAAM,WAAA,EAAY,CAAA;AAAA,oBACvB,KAAA,CAAA,aAAA,CAAC,GAAA,EAAA,EAAI,KAAA,EAAM,UAAA,EAAW;AAAA,qBAExB,KAAA,CAAA,aAAA,CAAC,GAAA,EAAA,EAAI,IAAI,CAAA,EAAA,EACN,GAAA,KAAQ,qBAAK,KAAA,CAAA,aAAA,CAAC,gBAAA,EAAA,EAAiB,GAAA,EAAU,CAAA,EACzC,QAAQ,CAAA,oBAAK,KAAA,CAAA,aAAA,CAAC,mBAAgB,GAAA,EAAU,CAC3C,CACF,CACF,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"DevxpPage.esm.js","sources":["../../src/components/DevxpPage.tsx"],"sourcesContent":["import * as React from 'react';\nimport { Header, Page, Content } from '@backstage/core-components';\nimport {\n useApi,\n fetchApiRef,\n discoveryApiRef,\n} from '@backstage/core-plugin-api';\nimport { Tabs, Tab, Box, Button } from '@material-ui/core';\nimport CompareArrowsIcon from '@material-ui/icons/CompareArrows';\nimport { DevxpClient } from '../api';\nimport { AnalyticsDashboard } from './AnalyticsDashboard';\nimport { DashboardContent } from './DashboardContent';\nimport { SettingsContent } from './SettingsContent';\nimport { DeveloperLeaderboard } from './DeveloperLeaderboard';\nimport { DeveloperDetails } from './DeveloperDetails';\nimport { DeveloperComparison } from './DeveloperComparison';\nimport { RepositoriesContent } from './RepositoriesContent';\n\ntype DevView = 'leaderboard' | 'details' | 'comparison';\n\nexport const DevxpPage = () => {\n const [tab, setTab] = React.useState(0);\n const fetchApi = useApi(fetchApiRef);\n const discoveryApi = useApi(discoveryApiRef);\n\n const api = React.useMemo(\n () => new DevxpClient({ fetchApi, discoveryApi }),\n [fetchApi, discoveryApi],\n );\n\n // Pre-load repo names so the Developers filter is populated before the user visits that tab\n const [repoOptions, setRepoOptions] = React.useState<string[]>([]);\n React.useEffect(() => {\n api\n .getRepositories({ pageSize: 200 })\n .then(data => setRepoOptions(data.repositories.map(r => r.repoName)))\n .catch(() => {});\n }, [api]);\n\n // Developers tab state\n const [devView, setDevView] = React.useState<DevView>('leaderboard');\n const [selectedUserId, setSelectedUserId] = React.useState('');\n const [compareList, setCompareList] = React.useState<string[]>([]);\n\n const handleViewDeveloper = (userId: string) => {\n setSelectedUserId(userId);\n setDevView('details');\n };\n\n const handleToggleCompare = (userId: string) => {\n setCompareList(prev =>\n prev.includes(userId) ? prev.filter(id => id !== userId) : [...prev.slice(-1), userId],\n );\n };\n\n const handleOpenComparison = () => {\n setDevView('comparison');\n };\n\n const handleBackToLeaderboard = () => {\n setDevView('leaderboard');\n };\n\n return (\n <Page themeId=\"tool\">\n <Header\n title=\"Developer Intelligence\"\n subtitle=\"DevXP Analytics & Developer Name Mapping\"\n />\n <Content>\n <Tabs\n value={tab}\n onChange={(_, v) => setTab(v)}\n indicatorColor=\"primary\"\n textColor=\"primary\"\n >\n <Tab label=\"Analytics\" />\n <Tab label=\"Developers\" />\n <Tab label=\"Repositories\" />\n <Tab label=\"Identity\" />\n <Tab label=\"Settings\" />\n </Tabs>\n <Box mt={3}>\n {tab === 0 && <AnalyticsDashboard api={api} />}\n\n {tab === 1 && (\n <Box>\n {devView === 'leaderboard' && (\n <>\n {compareList.length > 0 && (\n <Box mb={2} display=\"flex\" justifyContent=\"flex-end\">\n <Button\n variant=\"outlined\"\n color=\"primary\"\n size=\"small\"\n startIcon={<CompareArrowsIcon />}\n onClick={handleOpenComparison}\n >\n Compare ({compareList.length})\n </Button>\n </Box>\n )}\n <DeveloperLeaderboard\n api={api}\n onViewDeveloper={handleViewDeveloper}\n onCompareDeveloper={handleToggleCompare}\n compareList={compareList}\n repoOptions={repoOptions}\n />\n </>\n )}\n\n {devView === 'details' && (\n <DeveloperDetails\n api={api}\n userId={selectedUserId}\n onBack={handleBackToLeaderboard}\n onCompare={handleToggleCompare}\n isInCompare={compareList.includes(selectedUserId)}\n />\n )}\n\n {devView === 'comparison' && (\n <DeveloperComparison\n api={api}\n initialUserIds={compareList}\n onBack={handleBackToLeaderboard}\n />\n )}\n </Box>\n )}\n\n {tab === 2 && (\n <RepositoriesContent\n api={api}\n onViewDeveloper={userId => {\n handleViewDeveloper(userId);\n setTab(1);\n }}\n />\n )}\n {tab === 3 && <DashboardContent api={api} />}\n {tab === 4 && <SettingsContent api={api} />}\n </Box>\n </Content>\n </Page>\n );\n};\n"],"names":[],"mappings":"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"}