Skip to content

Commit 584dbf4

Browse files
authored
Merge pull request #195 from oss-slu/feature/issue_186_remove_inactive_users
Feature/issue 186 remove inactive users
2 parents 6c2d911 + 9e10631 commit 584dbf4

8 files changed

Lines changed: 372 additions & 60 deletions

File tree

Backend/dataCollection/formatJSON.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
import numpy as np
33
import pandas as pd
4+
from Backend.streakCalculation import calculate_current_streak
45

56

67
def format_json_data(raw_data, sprint = -1):
@@ -44,11 +45,17 @@ def format_json_data(raw_data, sprint = -1):
4445
avg_time_to_close = group['lead_time'].mean()
4546
total_issues_opened = len(group)
4647
total_issues_closed = group['state'].value_counts().get('closed', 0)
48+
if 'closed_at' in group.columns:
49+
closed_issue_dates = group[group['state'] == 'closed']['closed_at'].dropna().tolist()
50+
else:
51+
closed_issue_dates = []
52+
current_streak = calculate_current_streak(closed_issue_dates)
4753

4854
formatted_data['issues'][user] = {
4955
"average_time_to_close": avg_time_to_close if not np.isnan(avg_time_to_close) else 0,
5056
"total_issues_opened": total_issues_opened,
51-
"total_issues_closed": total_issues_closed
57+
"total_issues_closed": total_issues_closed,
58+
"currentStreak": current_streak
5259
}
5360

5461
#Process Pull Requests

Backend/streakCalculation.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from datetime import datetime, timedelta
2+
3+
ISSUE_THRESHOLD = 3
4+
5+
def get_week_start(date_obj):
6+
"""
7+
Returns the Monday of the week for a given data
8+
"""
9+
return (date_obj - timedelta(days=date_obj.weekday())).date()
10+
11+
def parse_closed_dates(closed_issue_dates):
12+
"""
13+
Converts a list of date strings or datetime objects into datetime objects
14+
Skips invalid or empty values
15+
"""
16+
parsed_dates = []
17+
18+
for value in closed_issue_dates:
19+
if not value:
20+
continue
21+
22+
if isinstance(value, datetime):
23+
parsed_dates.append(value)
24+
continue
25+
26+
try:
27+
parsed_dates.append(datetime.fromisoformat(value.replace("Z", "+00:00")))
28+
except (ValueError, AttributeError):
29+
continue
30+
31+
return parsed_dates
32+
33+
def group_issues_by_week(closed_issue_dates):
34+
"""
35+
Counts how many issues were closed in each week
36+
Returns a dictionary like:
37+
{
38+
week_start_date: count
39+
}
40+
"""
41+
weekly_counts = {}
42+
43+
parsed_dates = parse_closed_dates(closed_issue_dates)
44+
45+
for closed_date in parsed_dates:
46+
week_start = get_week_start(closed_date)
47+
weekly_counts[week_start] = weekly_counts.get(week_start, 0) + 1
48+
49+
return weekly_counts
50+
51+
def calculate_current_streak(closed_issue_dates, threshold=ISSUE_THRESHOLD, reference_date=None):
52+
"""
53+
Calculates the active streak based on weekly issue closure threshold
54+
55+
A streak increases by 1 for every consecutive week where
56+
the number of closed issues meets or exceeds the threshold
57+
58+
Args:
59+
closed_issue_dates: list of datetime objects or ISO date strings
60+
threshold: minimum number of issues closed in a week
61+
reference_date: optional datetime to use instead of current time
62+
63+
Returns:
64+
int: current streak count
65+
"""
66+
if reference_date is None:
67+
reference_date = datetime.now()
68+
69+
weekly_counts = group_issues_by_week(closed_issue_dates)
70+
71+
if not weekly_counts:
72+
return 0
73+
74+
current_week_start = get_week_start(reference_date)
75+
streak = 0
76+
77+
while weekly_counts.get(current_week_start, 0) >= threshold:
78+
streak += 1
79+
current_week_start -= timedelta(days=7)
80+
81+
return streak
82+
83+
def count_total_closed_issues(closed_issue_dates):
84+
"""
85+
Returns the total number of valid closed issue dates
86+
"""
87+
return len(parse_closed_dates(closed_issue_dates))

Backend/test_streakCalculation.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from datetime import datetime
2+
3+
from Backend.streakCalculation import (
4+
calculate_current_streak,
5+
group_issues_by_week,
6+
count_total_closed_issues
7+
)
8+
9+
def test_calculate_current_streak_consecutive_weeks():
10+
closed_issue_dates = [
11+
"2026-04-01T10:00:00",
12+
"2026-04-02T11:00:00",
13+
"2026-04-03T12:00:00",
14+
"2026-03-24T10:00:00",
15+
"2026-03-25T11:00:00",
16+
"2026-03-26T12:00:00",
17+
]
18+
19+
reference_date = datetime(2026, 4, 3)
20+
streak = calculate_current_streak(closed_issue_dates, threshold=3, reference_date=reference_date)
21+
22+
assert streak == 2
23+
24+
def test_calculate_current_streak_resets_when_week_missed():
25+
closed_issue_dates = [
26+
"2026-04-01T10:00:00",
27+
"2026-04-02T11:00:00",
28+
"2026-04-03T12:00:00",
29+
"2026-03-10T10:00:00",
30+
"2026-03-11T11:00:00",
31+
"2026-03-12T12:00:00",
32+
]
33+
34+
reference_date = datetime(2026, 4, 3)
35+
streak = calculate_current_streak(closed_issue_dates, threshold=3, reference_date=reference_date)
36+
37+
assert streak == 1
38+
39+
def test_calculate_current_streak_returns_zero_for_no_data():
40+
closed_issue_dates = []
41+
42+
reference_date = datetime(2026, 4, 3)
43+
streak = calculate_current_streak(closed_issue_dates, threshold=3, reference_date=reference_date)
44+
45+
assert streak == 0
46+
47+
48+
def test_group_issues_by_week_counts_correctly():
49+
closed_issue_dates = [
50+
"2026-04-01T10:00:00",
51+
"2026-04-02T11:00:00",
52+
"2026-04-08T12:00:00",
53+
]
54+
55+
weekly_counts = group_issues_by_week(closed_issue_dates)
56+
57+
assert len(weekly_counts) == 2
58+
assert sum(weekly_counts.values()) == 3
59+
60+
61+
def test_count_total_closed_issues_counts_valid_dates():
62+
closed_issue_dates = [
63+
"2026-04-01T10:00:00",
64+
"2026-04-02T11:00:00",
65+
None,
66+
"",
67+
"invalid-date"
68+
]
69+
70+
total_closed = count_total_closed_issues(closed_issue_dates)
71+
72+
assert total_closed == 2

Frontend/src/components/TopContributorsRepos.jsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,28 @@ 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" }}>
2425
{/* Render list of top contributors by activity count */}
2526
<div>
26-
<h3>Top Contributors</h3>
27+
<h3>
28+
Top Contributors{" "}
29+
<span
30+
tabIndex="0"
31+
aria-label="Leaderboard streak info"
32+
title="A streak increases for every consecutive 7-day period where a user closes at least 3 issues. Streak resets if threshold is not met."
33+
style={{ cursor: "pointer", border: "1px solid black", borderRadius: "50%", padding: "2px 6px", fontSize: "12px" }}
34+
>
35+
i
36+
</span>
37+
</h3>
2738
<ul>
2839
{topContributors.map((user) => (
2940
<li key={user.name}>
30-
{user.name} ({user.count})
41+
{user.name} 🔥 {user.currentStreak} {user.currentStreak === 1 ? "week" : "weeks"} streak ({user.count})
3142
</li>
3243
))}
3344
</ul>

Frontend/src/components/charts/__tests__/test_getTopContributorsAndRepos.test.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ const mockJSON = {
2424
it("counts contributor activity and sorts correctly", () => {
2525
const {topContributors} = getTopContributorsAndRepos(mockJSON, 5);
2626
// charlie: 4, alicce: 2, bob: 1
27-
expect(topContributors).toEqual([
28-
{ name: "charlie", count: 4 },
29-
{ name: "alice", count: 2 },
30-
{ name: "bob", count: 1 },
31-
]);
27+
expect(topContributors).toEqual(
28+
expect.arrayContaining([
29+
expect.objectContaining({ name: "charlie", count: 4 }),
30+
expect.objectContaining({ name: "alice", count: 2 }),
31+
expect.objectContaining({ name: "bob", count: 1 }),
32+
])
33+
);
3234
});
3335

3436
it("counts repository activity and sorts correctly", () => {

0 commit comments

Comments
 (0)