Skip to content

Commit d81df65

Browse files
author
sergiov-ni
authored
Update coding-hours.yml
1 parent 5e65ecd commit d81df65

File tree

1 file changed

+234
-74
lines changed

1 file changed

+234
-74
lines changed

.github/workflows/coding-hours.yml

Lines changed: 234 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,88 +2,248 @@ name: Coding‑hours report
22

33
on:
44
schedule:
5-
- cron: '0 0 * * 1' # every Monday at 00:00 UTC
6-
workflow_dispatch: # manual trigger
5+
- cron: '0 0 * * 1' # every Monday 00:00 UTC
6+
workflow_dispatch:
77
inputs:
88
window_start:
99
description: 'Report since YYYY‑MM‑DD'
1010
required: false
1111

1212
permissions:
13-
contents: write # needed for committing to the metrics branch
13+
contents: write
14+
pages: write
15+
id-token: write
1416

1517
jobs:
18+
###############################################################################
19+
# Job 1 – run git‑hours (Go), build badge, commit to `metrics`
20+
###############################################################################
1621
report:
22+
if: github.ref == 'refs/heads/develop'
1723
runs-on: ubuntu-latest
24+
25+
steps:
26+
- uses: actions/checkout@v4
27+
with: { fetch-depth: 0 }
28+
29+
- uses: actions/setup-go@v4
30+
with: { go-version: '1.24' }
31+
32+
- name: Install git‑hours v0.1.2
33+
run: |
34+
git clone --depth 1 --branch v0.1.2 https://github.com/trinhminhtriet/git-hours.git git-hours-src
35+
sed -i 's/go 1.24.1/go 1.24/' git-hours-src/go.mod
36+
(cd git-hours-src && go install .)
37+
# v0.1.2 has no --version flag; show help header instead
38+
git-hours -h | head -n 1
39+
40+
- name: Generate raw report
41+
run: |
42+
ARGS=""
43+
if [ -n "${{ github.event.inputs.window_start }}" ]; then
44+
ARGS+=" -since ${{ github.event.inputs.window_start }}"
45+
fi
46+
git-hours $ARGS > raw.txt
47+
cat raw.txt
48+
49+
# ──────────────────────────── PATCH ① auto‑detect JSON vs table ──
50+
- name: Convert to JSON
51+
run: |
52+
python - <<'PY'
53+
import json, re, pathlib
54+
raw_text = pathlib.Path('raw.txt').read_text().lstrip()
55+
56+
def table_to_json(lines):
57+
obj, th, tc = {}, 0, 0
58+
for line in lines:
59+
if not line or line.lower().startswith(('author','name','user','----','total')):
60+
continue
61+
parts = re.split(r'\s+', line.strip())
62+
if len(parts) < 3:
63+
continue
64+
commits = int(parts[-1])
65+
hours = int(parts[-2])
66+
email = ' '.join(parts[:-2])
67+
obj[email] = {"name": email, "hours": hours, "commits": commits}
68+
th += hours; tc += commits
69+
obj["total"] = {"name":"", "hours": th, "commits": tc}
70+
return obj
71+
72+
try: # already JSON?
73+
data = json.loads(raw_text)
74+
if "total" not in data:
75+
th = sum(v["hours"] for v in data.values())
76+
tc = sum(v["commits"] for v in data.values())
77+
data["total"] = {"name":"", "hours": th, "commits": tc}
78+
except json.JSONDecodeError:
79+
data = table_to_json(raw_text.splitlines())
80+
81+
pathlib.Path('git-hours.json').write_text(json.dumps(data, indent=2))
82+
PY
83+
# ────────────────────────────────────────────────────────────────
84+
85+
- name: Install jq
86+
run: sudo apt-get update -y && sudo apt-get install -y jq
87+
88+
- name: Build badge.json
89+
run: |
90+
HOURS=$(jq '.total.hours' git-hours.json)
91+
cat > badge.json <<EOF
92+
{ "schemaVersion":1,
93+
"label":"Coding hours",
94+
"message":"${HOURS}h",
95+
"color":"informational" }
96+
EOF
97+
98+
- name: Add workflow summary
99+
run: |
100+
echo "### ⏱ Coding‑hours report" >> "$GITHUB_STEP_SUMMARY"
101+
jq -r '
102+
to_entries
103+
| map(select(.key!="total"))
104+
| sort_by(-.value.hours)
105+
| (["Contributor","Hours","Commits"]
106+
, (map([.key, (.value.hours|tostring), (.value.commits|tostring)])))
107+
| @tsv' git-hours.json | column -t -s $'\t' >> "$GITHUB_STEP_SUMMARY"
108+
109+
- uses: actions/upload-artifact@v4
110+
with:
111+
name: git-hours-json
112+
path: git-hours.json
113+
retention-days: 30
114+
115+
# ──────────────────────────── PATCH ③ safer push logic ───────────
116+
- name: Push to metrics branch
117+
env:
118+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
119+
run: |
120+
git config --global user.name "git-hours bot"
121+
git config --global user.email "bot@github.com"
122+
123+
# Stash everything (tracked + untracked) so checkout can’t complain.
124+
git stash push --include-untracked --quiet
125+
126+
# Ensure we have the latest metrics from remote, if it exists.
127+
git fetch origin metrics || true
128+
if git show-ref --quiet refs/remotes/origin/metrics; then
129+
git switch --quiet metrics || git switch -c metrics origin/metrics
130+
git pull --ff-only origin metrics || true
131+
else
132+
git switch --orphan metrics
133+
git reset --hard
134+
fi
135+
136+
# Restore stashed badge.json + reports/
137+
git stash pop --quiet || true
138+
139+
mkdir -p reports
140+
cp git-hours.json "reports/git-hours-$(date +%F).json"
141+
git add reports badge.json
142+
git commit -m "chore(metrics): report $(date +%F)" || echo "No change"
143+
144+
git push https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }} metrics \
145+
|| git push --force-with-lease https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }} metrics
146+
147+
148+
###############################################################################
149+
# Job 2 – build Site & upload Pages artifact
150+
###############################################################################
151+
build-site:
152+
needs: report
153+
runs-on: ubuntu-latest
154+
155+
steps:
156+
- uses: actions/checkout@v4
157+
158+
- uses: actions/download-artifact@v4
159+
with: { name: git-hours-json, path: tmp }
160+
161+
- name: Build KPIs site
162+
run: |
163+
DATE=$(date +%F)
164+
mkdir -p site/data
165+
cp tmp/git-hours.json "site/data/git-hours-${DATE}.json"
166+
cp tmp/git-hours.json site/git-hours-latest.json
167+
168+
# (HTML generator unchanged)
169+
170+
python - <<'PY'
171+
import json, datetime, pathlib, html, textwrap
172+
data = json.load(open('tmp/git-hours.json'))
173+
total = data['total']
174+
labels = [html.escape(k) for k in data if k != 'total']
175+
rows = "\n".join(
176+
f"<tr><td>{l}</td><td>{data[l]['hours']}</td><td>{data[l]['commits']}</td></tr>"
177+
for l in labels)
178+
179+
page = f"""
180+
<!doctype html><html lang='en'><head>
181+
<meta charset='utf-8'>
182+
<title>Collaborator KPIs</title>
183+
<link rel='stylesheet'
184+
href='https://cdn.jsdelivr.net/npm/simpledotcss/simple.min.css'>
185+
<script src='https://cdn.jsdelivr.net/npm/sortable-tablesort/sortable.min.js' defer></script>
186+
<script src='https://cdn.jsdelivr.net/npm/chart.js'></script>
187+
<style>canvas{{max-height:400px}}</style>
188+
</head><body><main>
189+
<h1>Collaborator KPIs</h1>
190+
<p><em>Last updated {datetime.datetime.utcnow():%Y‑%m‑%d %H:%M UTC}</em></p>
191+
192+
<h2>Totals</h2>
193+
<ul>
194+
<li><strong>Hours</strong>: {total['hours']}</li>
195+
<li><strong>Commits</strong>: {total['commits']}</li>
196+
<li><strong>Contributors</strong>: {len(data)-1}</li>
197+
</ul>
198+
199+
<h2>Hours per contributor</h2>
200+
<canvas id='hoursChart'></canvas>
201+
202+
<h2>Detail table</h2>
203+
<table class='sortable'>
204+
<thead><tr><th>Contributor</th><th>Hours</th><th>Commits</th></tr></thead>
205+
<tbody>{rows}</tbody>
206+
</table>
207+
208+
<p>Historical JSON snapshots live in <code>/data</code>.</p>
209+
210+
<script>
211+
fetch('git-hours-latest.json')
212+
.then(r => r.json())
213+
.then(d => {{
214+
const labels = Object.keys(d).filter(k => k !== 'total');
215+
const hours = labels.map(l => d[l].hours);
216+
new Chart(document.getElementById('hoursChart'), {{
217+
type: 'bar',
218+
data: {{ labels, datasets:[{{label:'Hours',data:hours}}] }},
219+
options: {{
220+
responsive:true, maintainAspectRatio:false,
221+
plugins:{{legend:{{display:false}}}},
222+
scales:{{y:{{beginAtZero:true}}}}
223+
}}
224+
}});
225+
}});
226+
</script>
227+
</main></body></html>
228+
"""
229+
pathlib.Path('site/index.html').write_text(textwrap.dedent(page))
230+
PY
231+
232+
# ───────────────────── PATCH ② bump to v3 (uses artifact@v4) ──────
233+
- uses: actions/upload-pages-artifact@v3
234+
with: { path: site }
235+
# ───────────────────────────────────────────────────────────────────
236+
237+
###############################################################################
238+
# Job 3 – deploy to GitHub Pages
239+
###############################################################################
240+
deploy-pages:
241+
needs: build-site
242+
runs-on: ubuntu-latest
243+
environment:
244+
name: github-pages
245+
url: ${{ steps.deployment.outputs.page_url }}
246+
18247
steps:
19-
# 1️⃣ Check out full history
20-
- uses: actions/checkout@v4
21-
with:
22-
fetch-depth: 0
23-
24-
# 2️⃣ Set up Go (>=1.24 to support git-hours v0.1.2 go.mod)
25-
- name: Setup Go
26-
uses: actions/setup-go@v4
27-
with:
28-
go-version: '1.24'
29-
30-
# 3️⃣ Install git-hours (Go) v0.1.2 with go.mod patch
31-
- name: Install git-hours (Go) v0.1.2
32-
run: |
33-
git clone --depth 1 --branch v0.1.2 https://github.com/trinhminhtriet/git-hours.git git-hours-src
34-
cd git-hours-src
35-
sed -i 's/go 1.24.1/go 1.24/' go.mod
36-
go install .
37-
38-
# 4️⃣ Generate the report into a text file
39-
- name: Generate report
40-
run: |
41-
ARGS=""
42-
if [ -n "${{ github.event.inputs.window_start }}" ]; then
43-
ARGS+=" -since ${{ github.event.inputs.window_start }}"
44-
fi
45-
git-hours $ARGS > git-hours.txt
46-
47-
# 4️⃣½ Build a Shields.io badge from the JSON report
48-
- name: Build badge.json
49-
run: |
50-
HOURS=$(jq '.total.hours' git-hours.txt)
51-
cat > badge.json <<EOF
52-
{
53-
"schemaVersion": 1,
54-
"label": "Coding hours",
55-
"message": "${HOURS}h",
56-
"color": "informational"
57-
}
58-
EOF
59-
60-
# 5️⃣ Publish the raw report to the run summary
61-
- name: Add workflow summary
62-
run: |
63-
echo "### ⏱ Coding‑hours report" >> $GITHUB_STEP_SUMMARY
64-
echo '```' >> $GITHUB_STEP_SUMMARY
65-
cat git-hours.txt >> $GITHUB_STEP_SUMMARY
66-
echo '```' >> $GITHUB_STEP_SUMMARY
67-
68-
# 6️⃣ Upload the raw output as an artifact
69-
- uses: actions/upload-artifact@v4
70-
with:
71-
name: git-hours-output-${{ github.run_id }}
72-
path: git-hours.txt
73-
retention-days: 30
74-
75-
# 7️⃣ (Optional) Commit to metrics branch
76-
- name: Commit to metrics branch
77-
if: github.ref == 'refs/heads/develop'
78-
env:
79-
GH_TOKEN: ${{ secrets.GH_PAT }} # PAT needed for write access
80-
run: |
81-
git config --global user.name "git-hours bot"
82-
git config --global user.email "bot@github.com"
83-
git switch -C metrics || git checkout metrics
84-
mkdir -p reports
85-
mv git-hours.txt reports/git-hours-$(date +%F).txt
86-
# Keep badge.json at repo root (in metrics branch) for a stable URL
87-
git add reports badge.json
88-
git commit -m "chore(metrics): add report $(date +%F)" || echo "No change"
89-
git push https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }} metrics
248+
- id: deployment
249+
uses: actions/deploy-pages@v4

0 commit comments

Comments
 (0)