Refactored Github asset management code, changed to use domains and color-coded labels. See README for updated info.

This commit is contained in:
Ville Ranki 2021-02-07 23:43:37 +02:00
parent 090ccc3011
commit a84db65d93
2 changed files with 102 additions and 59 deletions

View File

@ -451,30 +451,42 @@ SVG files are printed as text currently, avoid printing them.
This module is disabled by default. This module is disabled by default.
### Github based hackerspace asset management ### Github based asset management
This module was written for asset (machines, tasks and todo) management with This module was written for asset (machines, tasks and todo) management by
GitHub issues and labels. mis-using GitHub issues and labels. It has been designed to be used with hackerspace
environment but can be extended to any purpose.
Create labels to github that represent machines (M: ) and spaces (S: ). #### Github project setup
When creating issues, assign machine and/or space labels for them.
* Create labels to github that represent for example different machines and spaces.
You can create any number of them.
* Define label colors for each type of asset. These are called domains in this module.
For example set all machine labels to be #B60205 and space labels to be #0E8A16. These
can be easily picked from color chooser.
* Edit the repository description and add a json block describing the
label domains and their colors. For example:
```
Hackerspace machines, todo and stuff. domains={ "machines": "#B60205", "spaces" : "#0E8A16"}
```
Make sure you type the description on one line - this is a silly Github limitation.
* When creating issues, assign machine and/or space labels for them.
For example, if a wood lathe is broken, create issue with labels For example, if a wood lathe is broken, create issue with labels
"M: Wood lathe" and "S: Wood working room". "Wood lathe" and "Wood working room".
Usage: #### Usage
* !ghproj setrepo [repository] - Set repository for this room (room admin only) * !ghproj setrepo [repository] - Set repository for this room (room admin only)
* !ghproj repo - Shows which repository this room tracks * !ghproj repo - Shows which repository this room tracks
* !ghproj rmrepo - Remove repository from this room (room admin only) * !ghproj rmrepo - Remove repository from this room (room admin only)
* !ghproj machines - List machine statuses for this room * !ghproj [domain] - List machine statuses in this domain
* !ghproj spaces - List statuses of spaces
Repository name must be in format TampereHacklab/Inventaario - you can Repository name must be in format TampereHacklab/Inventaario - you can
use this as a example to see how the labels work. use this as a example to see how the labels work.
In future this might be used as general purpose project management
module. PR's welcome!
## Bot setup ## Bot setup
* Create a Matrix user * Create a Matrix user

View File

@ -1,14 +1,75 @@
from github import Github from github import Github
import re
import json
from modules.common.module import BotModule from modules.common.module import BotModule
# Helper class with reusable code for github project stuff
class GithubProject:
def get_domains(description):
p = re.compile('domains=\{.*\}')
matches = json.loads(p.findall(description)[0][8:])
return matches
def get_domain(reponame, domain):
g = Github()
repo = g.get_repo(reponame)
domains = GithubProject.get_domains(repo.description)
if(not len(domains)):
return None, None
domain_color = domains.get(domain, None)
if not domain_color:
return None, None
open_issues = repo.get_issues(state='open')
domain_labels = []
labels = repo.get_labels()
for label in labels:
if label.color == domain_color[1:]:
domain_labels.append(label)
domain_issues = dict()
domain_ok = []
for label in domain_labels:
label_issues = []
for issue in open_issues:
if label in issue.labels:
label_issues.append(issue)
if len(label_issues):
domain_issues[label.name] = label_issues
else:
domain_ok.append(label.name)
return domain_issues, domain_ok
def domain_to_string(reponame, issues, ok):
text_out = reponame + ":\n"
for label in issues.keys():
text_out = text_out + f'{label}: '
for issue in issues[label]:
# todo: add {issue.html_url} when URL previews can be disabled
text_out = text_out + f'[{issue.title}] '
text_out = text_out + f'\n'
text_out = text_out + " OK : " + ', '.join(ok)
return text_out
def domain_to_html(reponame, issues, ok):
html_out = f'<b>{reponame}:</b> <br/>'
for label in issues.keys():
html_out = html_out + f'🚧 {label}: '
for issue in issues[label]:
# todo: add {issue.html_url} when URL previews can be disabled
html_out = html_out + f'[{issue.title}] '
html_out = html_out + f'<br/>'
html_out = html_out + " OK ☑️ " + ', '.join(ok)
return html_out
class MatrixModule(BotModule): class MatrixModule(BotModule):
def __init__(self, name): def __init__(self, name):
super().__init__(name) super().__init__(name)
self.repo_rooms = dict() self.repo_rooms = dict()
self.machine_prefix = "M: "
self.space_prefix = "S: "
async def matrix_message(self, bot, room, event): async def matrix_message(self, bot, room, event):
args = event.body.split() args = event.body.split()
@ -24,17 +85,15 @@ class MatrixModule(BotModule):
if args[0] == 'repo': if args[0] == 'repo':
await bot.send_text(room, f'Github repo for this room is {self.repo_rooms.get(room.room_id, "not set")}.') await bot.send_text(room, f'Github repo for this room is {self.repo_rooms.get(room.room_id, "not set")}.')
return return
if args[0] == 'machines':
domain = args[0]
reponame = self.repo_rooms.get(room.room_id, None) reponame = self.repo_rooms.get(room.room_id, None)
if reponame: if reponame:
await self.get_machine_status(bot, room, reponame, self.machine_prefix) issues, ok = GithubProject.get_domain(reponame, domain)
if issues or ok:
await self.send_domain_status(bot, room, reponame, issues, ok)
else: else:
await bot.send_text(room, f'No github repo set for this room. Use setrepo to set it.') await bot.send_text(room, f'No labels with domain {domain} found.')
return
if args[0] == 'spaces':
reponame = self.repo_rooms.get(room.room_id, None)
if reponame:
await self.get_machine_status(bot, room, reponame, self.space_prefix)
else: else:
await bot.send_text(room, f'No github repo set for this room. Use setrepo to set it.') await bot.send_text(room, f'No github repo set for this room. Use setrepo to set it.')
return return
@ -53,42 +112,14 @@ class MatrixModule(BotModule):
await bot.send_text(room, 'Unknown command') await bot.send_text(room, 'Unknown command')
async def get_machine_status(self, bot, room, reponame, prefix): async def send_domain_status(self, bot, room, reponame, issues, ok):
g = Github() text_out = GithubProject.domain_to_string(reponame, issues, ok)
repo = g.get_repo(reponame) html_out = GithubProject.domain_to_html(reponame, issues, ok)
open_issues = repo.get_issues(state='open')
labels = repo.get_labels()
machine_status = dict()
working_machines = []
for label in labels:
if prefix in label.name:
machine_name = label.name[len(prefix):]
machine_status[machine_name] = []
for issue in open_issues:
if label in issue.labels:
machine_status[machine_name].append(issue)
if not machine_status[machine_name]:
working_machines.append(machine_name)
del machine_status[machine_name]
text_out = reponame + ":\n"
html_out = f'<b>{reponame}:</b> <br/>'
for machine in machine_status.keys():
text_out = text_out + f'{machine}: '
html_out = html_out + f'🚧 {machine}: '
for issue in machine_status[machine]:
text_out = text_out + f'{issue.title} ({issue.html_url}) '
html_out = html_out + f'<a href="{issue.html_url}">{issue.title}</a> '
text_out = text_out + f'\n'
html_out = html_out + f'<br/>'
text_out = text_out + " OK : " + ', '.join(working_machines)
html_out = html_out + " OK ☑️ " + ', '.join(working_machines)
await bot.send_html(room, html_out, text_out) await bot.send_html(room, html_out, text_out)
def help(self): def help(self):
return 'Github hacklab asset management' return 'Github asset management'
def get_settings(self): def get_settings(self):
data = super().get_settings() data = super().get_settings()