-
-
Notifications
You must be signed in to change notification settings - Fork 120
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
remove debug job, restrict create-pull-request to only awards.txt, ad…
…d documentation
- Loading branch information
1 parent
8a764f0
commit e0ebd2f
Showing
3 changed files
with
185 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
name: extend-awards | ||
run-name: Extending awards | ||
on: | ||
pull_request: | ||
types: [ closed ] | ||
branches: | ||
- master | ||
jobs: | ||
if_merged: | ||
if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-python@v5 | ||
with: | ||
python-version: '3.13' | ||
- run: pip install requests | ||
- run: python extend-awards.py | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
GITHUB_CONTEXT: ${{ toJson(github) }} | ||
- uses: peter-evans/create-pull-request@v7 | ||
with: | ||
add-paths: awards.csv | ||
commit-message: Extending awards.csv | ||
title: Extending awards.csv | ||
body: A PR was merged that solves an issue and awards.csv should be extended. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Automatically extend awards.csv | ||
|
||
## Overview | ||
|
||
Whenever a pull request (PR) is merged in the [stacker.news](https://github.com/stackernews/stacker.news) repository, a [GitHub Action](https://docs.github.com/en/actions) is triggered: | ||
|
||
If the merged PR solves an issue with [award tags](https://github.com/stackernews/stacker.news?tab=readme-ov-file#contributing), | ||
the amounts due to the PR and issue authors are calculated and corresponding lines are added to the [awards.csv](https://github.com/stackernews/stacker.news/blob/master/awards.csv) file, | ||
and a PR is opened for this change. | ||
|
||
## Action | ||
|
||
The action is defined in [.github/workflows/extend-awards.yml](.github/workflows/extend-awards.yml). | ||
|
||
Filters on the event type and parameters ensure the action is [triggered only on merged PRs](https://stackoverflow.com/questions/60710209/trigger-github-actions-only-when-pr-is-merged). | ||
|
||
The primary job consists of several steps: | ||
- [checkout](https://github.com/actions/checkout) checks out the repository | ||
- [setup-python](https://github.com/actions/setup-python) installs [Python](https://en.wikipedia.org/wiki/Python_(programming_language)) | ||
- [pip](https://en.wikipedia.org/wiki/Pip_%28package_manager%29) installs the [requests](https://docs.python-requests.org/en/latest/index.html) module | ||
- a script (see below) is executed, which appends lines to [awards.csv](awards.csv) if needed | ||
- [create-pull-request](https://github.com/peter-evans/create-pull-request) looks for modified files and creates (or updates) a PR | ||
|
||
## Script | ||
|
||
The script is [extend-awards.py](extend-awards.py). | ||
|
||
The script extracts from the [environment](https://en.wikipedia.org/wiki/Environment_variable) an authentication token needed for the [GitHub REST API](https://docs.github.com/en/rest/about-the-rest-api/about-the-rest-api) and the [context](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs) containing the event details including the merged PR (formatted in [JSON](https://en.wikipedia.org/wiki/JSON)). | ||
|
||
In the merged PR's title and body it searches for the first [GitHub issue URL](https://github.com/stackernews/stacker.news/issues/) or any number with a hash symbol (#) prefix, and takes this as the issue being solved by the PR. | ||
|
||
Using the GitHub REST API it fetches the issue and analyzes its tags for difficulty and priority. | ||
|
||
It fetches the issue's timeline and counts the number of reviews completed with status 'changes requested' to calculate the amount reduction. | ||
|
||
It calculates the amounts due to the PR author and the issue author. | ||
|
||
It reads the existing awards.csv file to suppress appending redundant lines (same user, PR, and issue) and fill known receive methods (same user). | ||
|
||
Finally, it appends zero, one, or two lines to the awards.csv file. | ||
|
||
## Diagnostics | ||
|
||
In the GitHub web interface under 'Actions' each invokation of the action can be viewed, including environment and [output and errors](https://en.wikipedia.org/wiki/Standard_streams) of the script. First, the specific invokation is selected, then the job 'if_merged', then the step 'Run python extend-awards.py'. The environment is found by expanding the inner 'Run python extended-awards.py' on the first line. | ||
|
||
The normal output includes details about the issue number found, the amount calculation, or the reason for not appending lines. | ||
|
||
The error output may include a [Python traceback](https://realpython.com/python-traceback/) which helps to explain the error. | ||
|
||
The environment contains in GITHUB_CONTEXT the event details, which may be required to understand the error. | ||
|
||
## Security considerations | ||
|
||
The create-pull-request step requires [workflow permissions](https://github.com/peter-evans/create-pull-request#workflow-permissions). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import json, os, re, requests | ||
|
||
difficulties = {'good-first-issue':20000,'easy':100000,'medium':250000,'medium-hard':500000,'hard':1000000} | ||
priorities = {'low':0.5,'medium':1.5,'high':2,'urgent':3} | ||
ignored = ['huumn', 'ekzyis'] | ||
fn = 'awards.csv' | ||
|
||
sess = requests.Session() | ||
headers = {'Authorization':'Bearer %s' % os.getenv('GITHUB_TOKEN') } | ||
awards = [] | ||
|
||
def getIssue(n): | ||
url = 'https://api.github.com/repos/stackernews/stacker.news/issues/' + n | ||
r = sess.get(url, headers=headers) | ||
j = json.loads(r.text) | ||
return j | ||
|
||
def findIssueInPR(j): | ||
p = re.compile('(#|https://github.com/stackernews/stacker.news/issues/)([0-9]+)') | ||
for m in p.finditer(j['title']): | ||
return m.group(2) | ||
if not 'body' in j or j['body'] is None: | ||
return | ||
for s in j['body'].split('\n'): | ||
for m in p.finditer(s): | ||
return m.group(2) | ||
|
||
def addAward(user, kind, pr, issue, difficulty, priority, count, amount): | ||
if amount >= 1000000 and amount % 1000000 == 0: | ||
amount = str(int(amount / 1000000)) + 'm' | ||
elif amount >= 1000 and amount % 1000 == 0: | ||
amount = str(int(amount / 1000)) + 'k' | ||
for a in awards: | ||
if a[0] == user and a[1] == kind and a[2] == pr: | ||
print('found existing entry %s' % a) | ||
if a[8] != amount: | ||
print('warning: amount %s != %s' % (a[8], amount)) | ||
return | ||
if count < 1: | ||
count = '' | ||
addr = '???' | ||
for a in awards: | ||
if a[0] == user and a[9] != '???': | ||
addr = a[9] | ||
print('adding %s,%s,%s,%s,%s,%s,%s,,%s,%s,???' % (user, kind, pr, issue, difficulty, priority, count, amount, addr)) | ||
with open(fn, 'a') as f: | ||
print('%s,%s,%s,%s,%s,%s,%s,,%s,%s,???' % (user, kind, pr, issue, difficulty, priority, count, amount, addr), file=f) | ||
|
||
def countReviews(pr): | ||
url = 'https://api.github.com/repos/stackernews/stacker.news/issues/%s/timeline' % pr | ||
r = sess.get(url, headers=headers) | ||
j = json.loads(r.text) | ||
count = 0 | ||
for e in j: | ||
if e['event'] == 'reviewed' and e['state'] == 'changes_requested': | ||
count += 1 | ||
return count | ||
|
||
def checkPR(i): | ||
pr = str(i['number']) | ||
print('pr %s' % pr) | ||
n = findIssueInPR(i) | ||
if not n: | ||
print('pr %s does not solve an issue' % pr) | ||
return | ||
print('solves issue %s' % n) | ||
j = getIssue(n) | ||
difficulty = '' | ||
amount = 0 | ||
priority = '' | ||
multiplier = 1 | ||
for l in j['labels']: | ||
for d in difficulties: | ||
if l['name'] == 'difficulty:' + d: | ||
difficulty = d | ||
amount = difficulties[d] | ||
for p in priorities: | ||
if l['name'] == 'priority:' + p: | ||
priority = p | ||
multiplier = priorities[p] | ||
if amount * multiplier <= 0: | ||
print('issue gives no award') | ||
return | ||
count = countReviews(pr) | ||
if count >= 10: | ||
print('too many reviews, no award') | ||
return | ||
if count > 0: | ||
print('%d reviews, %d%% reduction' % (count, count * 10)) | ||
award = amount * multiplier * (10 - count) / 10 | ||
print('award is %d' % award) | ||
if i['user']['login'] not in ignored: | ||
addAward(i['user']['login'], 'pr', '#' + pr, '#' + n, difficulty, priority, count, award) | ||
if j['user']['login'] not in ignored: | ||
count = 0 | ||
addAward(j['user']['login'], 'issue', '#' + pr, '#' + n, difficulty, priority, count, int(award / 10)) | ||
|
||
with open(fn, 'r') as f: | ||
for s in f: | ||
s = s.split('\n')[0] | ||
awards.append(s.split(',')) | ||
|
||
j = json.loads(os.getenv('GITHUB_CONTEXT')) | ||
checkPR(j['event']['pull_request']) |