Skip to content

Commit 9e10631

Browse files
committed
Fix inactive user filtering using latest sprint data (removes stale contributors like hcaballero2)
1 parent a097157 commit 9e10631

4 files changed

Lines changed: 141 additions & 61 deletions

File tree

Frontend/src/components/TopContributorsRepos.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ const TOP_N = 5;
1717

1818
const TopContributorsRepos = () => {
1919
// Converting repo JSON structure into event-like objects
20-
const {topContributors, topRepos} = getTopContributorsAndRepos(lifetimeData, TOP_N);
20+
const selectedRepo = "core_desk";
21+
const {topContributors, topRepos} = getTopContributorsAndRepos(lifetimeData, TOP_N, selectedRepo);
2122

2223
return (
2324
<div style={{ display: "flex", gap: "30px" }}>

Frontend/src/features/team-stats/utils/teamStatsHelper.js

Lines changed: 73 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,56 @@
77
*/
88
import sprintData from "../../../../../data/sprint_data.json";
99

10-
const activeUsers = new Set();
10+
const activeUsersByRepo = {};
11+
const latestSprintByRepo = {};
1112

12-
Object.values(sprintData).forEach(repo => {
13-
if (repo.issues) {
14-
Object.keys(repo.issues).forEach(user => activeUsers.add(user));
13+
// This finds the latest sprint per repo
14+
Object.entries(sprintData).forEach(([repoName]) => {
15+
const match = repoName.match(/(.*)_sprint_(\d+)$/);
16+
if (!match) return;
17+
18+
const baseRepo = match[1];
19+
const sprintNum = parseInt(match[2]);
20+
21+
if (
22+
!latestSprintByRepo[baseRepo] ||
23+
sprintNum > latestSprintByRepo[baseRepo].sprint
24+
) {
25+
latestSprintByRepo[baseRepo] = {
26+
sprint: sprintNum,
27+
fullKey: repoName,
28+
};
29+
}
30+
});
31+
32+
// This builds active users per repo (latest sprint only)
33+
Object.entries(latestSprintByRepo).forEach(([repoName, { fullKey }]) => {
34+
const repoData = sprintData[fullKey];
35+
const normalizedRepo = repoName.toLowerCase().trim();
36+
37+
activeUsersByRepo[normalizedRepo] = new Set();
38+
39+
if (repoData.issues) {
40+
Object.keys(repoData.issues).forEach(user =>
41+
activeUsersByRepo[normalizedRepo].add(user)
42+
);
1543
}
16-
if (repo.pull_requests) {
17-
Object.keys(repo.pull_requests).forEach(user => activeUsers.add(user));
44+
45+
if (repoData.pull_requests) {
46+
Object.keys(repoData.pull_requests).forEach(user =>
47+
activeUsersByRepo[normalizedRepo].add(user)
48+
);
1849
}
19-
if (repo.commits) {
20-
Object.keys(repo.commits).forEach(user => activeUsers.add(user));
50+
51+
if (repoData.commits) {
52+
Object.keys(repoData.commits).forEach(user =>
53+
activeUsersByRepo[normalizedRepo].add(user)
54+
);
2155
}
2256
});
2357

58+
// USERS
59+
2460
export const getUniqueUsers = (lifetimeData) => {
2561
return [
2662
"all",
@@ -31,73 +67,61 @@ export const getUniqueUsers = (lifetimeData) => {
3167
...Object.keys(repo.pull_requests ?? {}),
3268
...Object.keys(repo.commits ?? {})
3369
])
34-
.filter(user => activeUsers.has(user))
70+
.filter(user =>
71+
Object.values(activeUsersByRepo).some(set => set.has(user))
72+
)
3573
)
3674
)
3775
];
3876
};
39-
/*
40-
Extract unique team (repository) names from the lifetime data.
41-
Args:
42-
lifetimeData (Object): The parsed JSON object of repository data.
43-
Returns:
44-
Array: List of team names, with "All Teams" as the first element.
45-
*/
77+
4678
export const getUniqueTeams = (lifetimeData) => {
4779
return ["All Teams", ...Object.keys(lifetimeData)];
4880
};
49-
/*
50-
Extract unique users from a specific repository, or all if "All Teams".
51-
Args:
52-
lifetimeData (Object): The parsed JSON object of repository data.
53-
repo (String): The specific repository name to filter by.
54-
Returns:
55-
Array: List of unique usernames for that repo, with "all" as the first element.
56-
*/
81+
5782
export const getUsersByRepo = (lifetimeData, repo) => {
5883
if (repo === "All Teams" || !repo) {
5984
return getUniqueUsers(lifetimeData);
6085
}
61-
86+
87+
const normalizedRepo = repo.toLowerCase().trim();
6288
const repoData = lifetimeData[repo] || {};
89+
6390
return [
6491
"all",
6592
...Array.from(
6693
new Set([
6794
...Object.keys(repoData.issues ?? {}),
6895
...Object.keys(repoData.pull_requests ?? {}),
6996
...Object.keys(repoData.commits ?? {})
70-
].filter(user => activeUsers.has(user)))
97+
].filter(user => activeUsersByRepo[normalizedRepo]?.has(user)))
7198
)
7299
];
73100
};
74-
/*
75-
Calculate time-based metrics (e.g., average time to close or merge) for a specific user or all users.
76-
Args:
77-
lifetimeData (Object): The parsed JSON object of repository data.
78-
category (String): The category to parse ('issues' or 'pull_requests').
79-
metric (String): The specific metric key to retrieve.
80-
user (String): The selected username, or "all".
81-
Returns:
82-
Array: A list of objects formatted as { label: username, value: average_metric }.
83-
*/
101+
102+
//TIME DATA
103+
84104
export const buildTimeData = (lifetimeData, category, metric, user) => {
85105
const userValues = {};
86106

87-
Object.values(lifetimeData).forEach((repo) => {
107+
Object.entries(lifetimeData).forEach(([repoName, repo]) => {
108+
const normalizedRepo = repoName.toLowerCase().trim();
88109
const section = repo[category] ?? {};
89110

90111
if (user === "all") {
91112
Object.entries(section).forEach(([username, metrics]) => {
92-
if (!activeUsers.has(username)) return; // Skip users with no activity in sprint data
113+
if (!activeUsersByRepo[normalizedRepo]?.has(username)) return;
93114

94115
if (metrics[metric] != null) {
95116
if (!userValues[username]) userValues[username] = [];
96117
userValues[username].push(metrics[metric]);
97118
}
98119
});
99120
} else {
100-
if (activeUsers.has(user) && section[user] && section[user][metric] != null) {
121+
if (
122+
activeUsersByRepo[normalizedRepo]?.has(user) &&
123+
section[user]?.[metric] != null
124+
) {
101125
if (!userValues[user]) userValues[user] = [];
102126
userValues[user].push(section[user][metric]);
103127
}
@@ -106,28 +130,26 @@ export const buildTimeData = (lifetimeData, category, metric, user) => {
106130

107131
return Object.entries(userValues).map(([username, values]) => ({
108132
label: username,
109-
value: parseFloat((values.reduce((a, b) => a + b, 0) / values.length).toFixed(2)),
133+
value: parseFloat(
134+
(values.reduce((a, b) => a + b, 0) / values.length).toFixed(2)
135+
),
110136
}));
111137
};
112138

113-
/*
114-
Aggregate volume metrics (opened, closed, merged, commits) for a specific user.
115-
Args:
116-
lifetimeData (Object): The parsed JSON object of repository data.
117-
user (String): The selected username, or "all".
118-
Returns:
119-
Object: Key-value pairs of aggregated volume metrics.
120-
*/
139+
//VOLUME DATA
140+
121141
export const buildVolumeData = (lifetimeData, user) => {
122142
let issuesOpened = 0, issuesClosed = 0, prsOpened = 0, prsMerged = 0, commits = 0;
123143

124-
Object.values(lifetimeData).forEach((repo) => {
144+
Object.entries(lifetimeData).forEach(([repoName, repo]) => {
145+
const normalizedRepo = repoName.toLowerCase().trim();
146+
125147
const issues = repo.issues ?? {};
126148
const prs = repo.pull_requests ?? {};
127149
const repoCommits = repo.commits ?? {};
128150

129151
const addMetrics = (u) => {
130-
if (!activeUsers.has(u)) return;
152+
if (!activeUsersByRepo[normalizedRepo]?.has(u)) return;
131153

132154
issuesOpened += Number(issues[u]?.total_issues_opened) || 0;
133155
issuesClosed += Number(issues[u]?.total_issues_closed) || 0;
@@ -155,4 +177,4 @@ export const buildVolumeData = (lifetimeData, user) => {
155177
"PRs Merged": prsMerged,
156178
Commits: commits,
157179
};
158-
}
180+
};

Frontend/src/utils/getTopContributorsAndRepos.js

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,54 @@
1414

1515
import sprintData from "../../../data/sprint_data.json";
1616

17-
const activeUsers = new Set();
17+
const activeUsersByRepo = {};
18+
const latestSprintByRepo = {};
19+
20+
// STEP 1: find latest sprint for each repo
21+
Object.entries(sprintData).forEach(([repoName]) => {
22+
const match = repoName.match(/(.*)_sprint_(\d+)$/);
23+
if (!match) return;
24+
25+
const baseRepo = match[1];
26+
const sprintNum = parseInt(match[2]);
27+
28+
if (
29+
!latestSprintByRepo[baseRepo] ||
30+
sprintNum > latestSprintByRepo[baseRepo].sprint
31+
) {
32+
latestSprintByRepo[baseRepo] = {
33+
sprint: sprintNum,
34+
fullKey: repoName,
35+
};
36+
}
37+
});
38+
39+
// STEP 2: build active users ONLY from latest sprint
40+
Object.entries(latestSprintByRepo).forEach(([repoName, { fullKey }]) => {
41+
const repoData = sprintData[fullKey];
42+
const normalizedRepo = repoName.toLowerCase().trim();
43+
activeUsersByRepo[normalizedRepo] = new Set();
44+
45+
if (repoData.issues) {
46+
Object.keys(repoData.issues).forEach(user =>
47+
activeUsersByRepo[normalizedRepo].add(user)
48+
);
49+
}
50+
51+
if (repoData.pull_requests) {
52+
Object.keys(repoData.pull_requests).forEach(user =>
53+
activeUsersByRepo[normalizedRepo].add(user)
54+
);
55+
}
56+
57+
if (repoData.commits) {
58+
Object.keys(repoData.commits).forEach(user =>
59+
activeUsersByRepo[normalizedRepo].add(user)
60+
);
61+
}
62+
});
63+
64+
/*const activeUsers = new Set();
1865
1966
Object.values(sprintData).forEach(repo => {
2067
if (repo.issues) {
@@ -26,10 +73,10 @@ Object.values(sprintData).forEach(repo => {
2673
if (repo.commits) {
2774
Object.keys(repo.commits).forEach(user => activeUsers.add(user));
2875
}
29-
});
76+
});*/
3077

3178

32-
export function getTopContributorsAndRepos(lifetimeData, topN) {
79+
export function getTopContributorsAndRepos(lifetimeData, topN, selectedRepo) {
3380
const contributorStats = {};
3481
const repoStats = {};
3582
const contributorIssuesClosed = {};
@@ -39,6 +86,8 @@ export function getTopContributorsAndRepos(lifetimeData, topN) {
3986

4087
// Iterate through every repository in the organization
4188
Object.entries(lifetimeData).forEach(([repoName, repoData]) => {
89+
if (selectedRepo && repoName !== selectedRepo) return;
90+
4291
let repoTotalActivity = 0;
4392

4493
// Helper to safely add metrics to a user's total and the repo's total
@@ -54,10 +103,17 @@ export function getTopContributorsAndRepos(lifetimeData, topN) {
54103
}
55104
};
56105

106+
const cleanedRepoName = repoName.toLowerCase().trim();
107+
const repoActiveUsers = activeUsersByRepo[cleanedRepoName];
108+
109+
57110
// Tally Issues (Opened + Closed)
58111
if (repoData.issues) {
59112
Object.entries(repoData.issues).forEach(([user, stats]) => {
60-
if (import.meta.env.MODE !== "test" && !activeUsers.has(user)) return;
113+
if (
114+
import.meta.env.MODE !== "test" &&
115+
(!repoActiveUsers || !repoActiveUsers.has(user))
116+
) return;
61117

62118
addActivity(user, stats.total_issues_opened);
63119
addActivity(user, stats.total_issues_closed, true);
@@ -67,7 +123,10 @@ export function getTopContributorsAndRepos(lifetimeData, topN) {
67123
// Tally Pull Requests (Opened + Merged)
68124
if (repoData.pull_requests) {
69125
Object.entries(repoData.pull_requests).forEach(([user, stats]) => {
70-
if (import.meta.env.MODE !== "test" && !activeUsers.has(user)) return;
126+
if (
127+
import.meta.env.MODE !== "test" &&
128+
(!repoActiveUsers || !repoActiveUsers.has(user))
129+
) return;
71130

72131
addActivity(user, stats.total_prs_opened);
73132
addActivity(user, stats.total_prs_merged);

Frontend/vite.config.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ export default defineConfig({
1111
},
1212
server: {
1313
fs: {
14-
allow: [
15-
new URL('..', import.meta.url).pathname,
16-
],
14+
allow: ['..'],
1715
},
18-
}
16+
},
1917
});

0 commit comments

Comments
 (0)