From a84db65d934ea752a87113198dc3edc311b3cdcb Mon Sep 17 00:00:00 2001 From: Ville Ranki Date: Sun, 7 Feb 2021 23:43:37 +0200 Subject: [PATCH] Refactored Github asset management code, changed to use domains and color-coded labels. See README for updated info. --- README.md | 36 ++++++++----- modules/ghproj.py | 125 +++++++++++++++++++++++++++++----------------- 2 files changed, 102 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 815fab1..2ad48c4 100644 --- a/README.md +++ b/README.md @@ -451,30 +451,42 @@ SVG files are printed as text currently, avoid printing them. 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 -GitHub issues and labels. +This module was written for asset (machines, tasks and todo) management by +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: ). -When creating issues, assign machine and/or space labels for them. +#### Github project setup + +* 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 -"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 repo - Shows which repository this room tracks * !ghproj rmrepo - Remove repository from this room (room admin only) -* !ghproj machines - List machine statuses for this room -* !ghproj spaces - List statuses of spaces +* !ghproj [domain] - List machine statuses in this domain Repository name must be in format TampereHacklab/Inventaario - you can 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 * Create a Matrix user diff --git a/modules/ghproj.py b/modules/ghproj.py index cf545f3..1093a96 100644 --- a/modules/ghproj.py +++ b/modules/ghproj.py @@ -1,14 +1,75 @@ from github import Github +import re +import json 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'{reponame}:
' + 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'
' + + html_out = html_out + " OK ☑️ " + ', '.join(ok) + return html_out class MatrixModule(BotModule): def __init__(self, name): super().__init__(name) self.repo_rooms = dict() - self.machine_prefix = "M: " - self.space_prefix = "S: " async def matrix_message(self, bot, room, event): args = event.body.split() @@ -24,20 +85,18 @@ class MatrixModule(BotModule): 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")}.') return - if args[0] == 'machines': - reponame = self.repo_rooms.get(room.room_id, None) - if reponame: - await self.get_machine_status(bot, room, reponame, self.machine_prefix) + + domain = args[0] + reponame = self.repo_rooms.get(room.room_id, None) + if reponame: + issues, ok = GithubProject.get_domain(reponame, domain) + if issues or ok: + await self.send_domain_status(bot, room, reponame, issues, ok) else: - await bot.send_text(room, f'No github repo set for this room. Use setrepo to set it.') - 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: - await bot.send_text(room, f'No github repo set for this room. Use setrepo to set it.') - return + await bot.send_text(room, f'No labels with domain {domain} found.') + else: + await bot.send_text(room, f'No github repo set for this room. Use setrepo to set it.') + return if len(args) == 2: if args[0] == 'setrepo': @@ -53,42 +112,14 @@ class MatrixModule(BotModule): await bot.send_text(room, 'Unknown command') - async def get_machine_status(self, bot, room, reponame, prefix): - g = Github() - repo = g.get_repo(reponame) - 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'{reponame}:
' - 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'{issue.title} ' - text_out = text_out + f'\n' - html_out = html_out + f'
' - - text_out = text_out + " OK : " + ', '.join(working_machines) - html_out = html_out + " OK ☑️ " + ', '.join(working_machines) + async def send_domain_status(self, bot, room, reponame, issues, ok): + text_out = GithubProject.domain_to_string(reponame, issues, ok) + html_out = GithubProject.domain_to_html(reponame, issues, ok) await bot.send_html(room, html_out, text_out) def help(self): - return 'Github hacklab asset management' + return 'Github asset management' def get_settings(self): data = super().get_settings()