Skip to content

Commit c38dd39

Browse files
committed
feature: added bus factor
1 parent 7c1f847 commit c38dd39

File tree

5 files changed

+70
-1
lines changed

5 files changed

+70
-1
lines changed

git_analytics/analyzers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .authors_statistics import AuthorsStatisticsAnalyzer
2+
from .bus_factor_post import bus_factor_post
23
from .commit_type import CommitTypeAnalyzer
34
from .commits_summary import CommitsSummaryAnalyzer
45
from .historical_statistics import HistoricalStatisticsAnalyzer
@@ -7,6 +8,7 @@
78

89
__all__ = [
910
"AuthorsStatisticsAnalyzer",
11+
"bus_factor_post",
1012
"CommitTypeAnalyzer",
1113
"CommitsSummaryAnalyzer",
1214
"HistoricalStatisticsAnalyzer",
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from typing import Any, Dict
2+
3+
4+
def _bus_factor(contributions: Dict[str, int], threshold: float = 0.5) -> int:
5+
total = sum(contributions.values())
6+
acc = 0
7+
for _, lines in sorted(contributions.items(), key=lambda x: x[1], reverse=True):
8+
acc += lines
9+
if acc / total >= threshold:
10+
return len([a for a in contributions if contributions[a] >= lines])
11+
return 1
12+
13+
14+
def bus_factor_post(data: Dict[str, Any]) -> int:
15+
author_lines = dict(data.get("authors_statistics", {})).get("authors")
16+
17+
if author_lines:
18+
author_data = {}
19+
for author, item in author_lines.items():
20+
author_data[author] = item.get("insertions", 0) + item.get("deletions", 0)
21+
22+
if len(author_data) < 2:
23+
return 1
24+
25+
return _bus_factor(author_data)
26+
27+
return 1

git_analytics/engine.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from pathlib import Path
44
from typing import Any, Dict, Optional, Tuple
55

6+
from git_analytics.analyzers import bus_factor_post
67
from git_analytics.entities import AnalyticsResult
78
from git_analytics.interfaces import CommitSource
89

@@ -86,6 +87,11 @@ def run(
8687
analyzer.process(commit)
8788

8889
result = {analyzer.name: analyzer.result() for analyzer in analyzers}
90+
91+
# post-process
92+
result["post_data"] = {}
93+
result["post_data"]["bus_factor"] = bus_factor_post(result)
94+
8995
if self._additional_data:
9096
result["additional_data"] = self._additional_data
9197
return result

git_analytics/static/index.html

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ <h2>Overview</h2>
9090
</div>
9191
</div>
9292
</div>
93+
<div class="col-md-2">
94+
<div class="card h-100 w-100">
95+
<div class="card-header">
96+
<span>Bus Factor</span>
97+
<i class="bi bi-info-circle"
98+
data-bs-toggle="tooltip"
99+
data-bs-placement="right"
100+
title="The bus factor is a measurement of the risk resulting from information and capabilities not being shared among team members. It indicates the minimum number of developers that have to leave a project before the project becomes incapacitated.">
101+
</i>
102+
</div>
103+
<div class="card-body">
104+
<span id="busFactor"></span>
105+
</div>
106+
</div>
107+
</div>
93108
</div>
94109
<div class="row mt-4">
95110
<div class="col-md-12">
@@ -320,7 +335,7 @@ <h2>Commits</h2>
320335
<!-- footer -->
321336
<footer class="bg-dark text-white py-3 mt-auto">
322337
<div class="container">
323-
<span>&copy; 2025 ver 0.1.14</span>
338+
<span>&copy; 2026 ver 0.1.14</span>
324339
</div>
325340
</footer>
326341

git_analytics/static/js/git-analytics.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ async function loadAndRender(type, value, timeIntervalLabel) {
6464

6565
// render stats
6666
renderGeneralStatistics(stats, timeIntervalLabel);
67+
renderBusFactor(stats);
6768
renderWeeklyCommitTypes(stats.commit_type.commit_type_by_week);
6869

6970
renderInsDelLinesByAuthors(stats.authors_statistics.authors);
@@ -158,6 +159,24 @@ function renderGeneralStatistics(stats, rangeLabel) {
158159
`;
159160
}
160161

162+
function renderBusFactor(stats) {
163+
const busFactorEl = document.getElementById("busFactor");
164+
busFactorVal= stats.post_data.bus_factor;
165+
166+
let colorClass = "text-success";
167+
if (busFactorVal === 1) {
168+
colorClass = "text-danger";
169+
} else if (busFactorVal === 2) {
170+
colorClass = "text-warning";
171+
}
172+
173+
busFactorEl.innerHTML = `
174+
<div class="d-flex align-items-center justify-content-center h-100">
175+
<strong class="h1 ${colorClass}">${busFactorVal}</strong>
176+
</div>
177+
`;
178+
}
179+
161180
function renderChart(id, config) {
162181
if (CHARTS[id]) {
163182
CHARTS[id].destroy();

0 commit comments

Comments
 (0)