Added ability to create new issues
This commit is contained in:
parent
bed8f7ac8d
commit
1f3073476a
|
|
@ -2,16 +2,20 @@ gitea_instance: git.arcticsoftware.no
|
||||||
gitea_token: ABC123ABC1ABC123ABC1ABC123ABC1ABC123ABC1ABC123ABC1ABC123ABC123A
|
gitea_token: ABC123ABC1ABC123ABC1ABC123ABC1ABC123ABC1ABC123ABC1ABC123ABC123A
|
||||||
gitea_owner: someowner
|
gitea_owner: someowner
|
||||||
gitea_repo: somerepo
|
gitea_repo: somerepo
|
||||||
|
gitea_bug_label: 1
|
||||||
|
gitea_feature_label: 1
|
||||||
|
feature_issue_room: '!someroom:matrix.server.com'
|
||||||
|
bug_issue_room: '!someotherrroom:matrix.server.com'
|
||||||
about_text: |
|
about_text: |
|
||||||
Jeg er ment som en bro mellom Arctic Software sitt kodedeponi og
|
Jeg er ment som en bro mellom Arctic Software sitt kodedeponi og
|
||||||
matrix chatten. Du kan sende forskjellige kommandoer til meg og
|
matrix chatten. Du kan sende forskjellige kommandoer til meg og
|
||||||
jeg vil utføre de oppgavene du forespør. Her er en liste over
|
jeg vil utføre de oppgavene du forespør. Her er en liste over
|
||||||
kommandoene du kan bruke:<br /><br />
|
kommandoene du kan bruke:<br /><br />
|
||||||
|
|
||||||
<code>"!om"</code> eller <code>"!hjelp"</code>:<br />
|
<code>!om</code> eller <code>"!hjelp"</code>:<br />
|
||||||
Viser denne teksten<br /><br />
|
Viser denne teksten<br /><br />
|
||||||
|
|
||||||
<code>"!saker" [status]</code>:<br />
|
<code>!saker [status]</code>:<br />
|
||||||
Viser en liste over aktive oppgaver i kodedeponiet. Oppgaver kan
|
Viser en liste over aktive oppgaver i kodedeponiet. Oppgaver kan
|
||||||
være buggs som må løses, ønskede fremtidige funksjoner eller andre
|
være buggs som må løses, ønskede fremtidige funksjoner eller andre
|
||||||
forespørsler og saker. Å sende status er valgfritt og vil filtrere
|
forespørsler og saker. Å sende status er valgfritt og vil filtrere
|
||||||
|
|
@ -19,36 +23,72 @@ about_text: |
|
||||||
'lukket', eller 'alle' for å velge hva slags saker du ønsker å
|
'lukket', eller 'alle' for å velge hva slags saker du ønsker å
|
||||||
vise.<br /><br />
|
vise.<br /><br />
|
||||||
|
|
||||||
Eksempel: "!saker" eller "!saker åpen"<br /><br />
|
<code>Eksempel: "!saker" eller "!saker lukket"</code><br /><br />
|
||||||
|
|
||||||
<code>"!eldre"</code>:<br />
|
<code>!ny [type] "[tittel]" "[beskrivelse]"</code>:<br />
|
||||||
Viser en liste over ferdige/utgåtte oppgaver i kodedeponiet.<br /><br />
|
|
||||||
|
|
||||||
<code>"!ny" [type]</code>:<br />
|
|
||||||
Oppretter en ny oppgave. Følg opp kommandoen med hva slags type
|
Oppretter en ny oppgave. Følg opp kommandoen med hva slags type
|
||||||
oppgave du vil opprette, dette kan være enten "bug" eller
|
oppgave du vil opprette og en tittel og en lengre beskrivelse.
|
||||||
"forbedring".<br /><br />
|
Type kan være enten "bug" eller "forbedring". Tittelen kan maks
|
||||||
|
være 250 tegn. Men prøv å hold tittelen så kort og konsis som
|
||||||
|
mulig.<br /><br />
|
||||||
|
|
||||||
Etter at du har kjørt denne kommandoen, vil jeg sende deg en PM
|
Jeg vil så lagre oppgaven i kodedeponiets sakssystem, samtidig
|
||||||
hvor du må fullføre beskrivelsen av oppgaven. Jeg vil så lagre
|
som jeg vil opprette en tråd på matrix serveren brukere kan
|
||||||
oppgaven i kodedeponiets sakssystem, samtidig som jeg vil
|
delta i, for å chatte og diskutere videre om oppgaven.<br /><br />
|
||||||
opprette en tråd på matrix serveren brukere kan delta i, så
|
|
||||||
de kan chatte og diskutere videre om oppgaven.<br /><br />
|
|
||||||
|
|
||||||
Eksempel: "!ny bug"<br /><br />
|
<code>Eksempel: !ny bug "Dette er en passe kort tittel" "Lengre beskrivelse"</code><br /><br />
|
||||||
|
|
||||||
<code>"!sak" [saksnr]</code>:<br />
|
<code>!sak [saksnr]</code>:<br />
|
||||||
Viser detaljer om en oppgave/sak. Følg opp kommandoen med et
|
Viser detaljer om en oppgave/sak. Følg opp kommandoen med et
|
||||||
saksnr.<br /><br />
|
saksnr.<br /><br />
|
||||||
|
|
||||||
Eksempel: "!sak 83"<br /><br />
|
<code>Eksempel: "!sak 83"</code><br /><br />
|
||||||
|
|
||||||
Denne botten er lagd/kodet av Helge-Mikael Nordgård og
|
Denne botten er lagd/kodet av Helge-Mikael Nordgård og
|
||||||
eventuelle spørsmål om bruk/utvikling av den kan rettes til
|
eventuelle spørsmål om bruk/utvikling av den kan rettes til
|
||||||
meg <a href="mailto:surface-fancy-deem@duck.com">per mail</a>.
|
meg <a href="mailto:surface-fancy-deem@duck.com">per mail</a>.<br /><br />
|
||||||
|
|
||||||
|
Koden for denne botten er lisensiert under AGPL-3.0 og er fritt
|
||||||
|
tilgjengelig på mitt private
|
||||||
|
<a href="https://git.outlands.no/heno/devops-bot">git deponi</a>.
|
||||||
|
issue_bug_text: |
|
||||||
|
Ny 🪲 rapport fra {user} ({userId}):<br /><br />
|
||||||
|
|
||||||
|
<strong>{title}</strong> <em>(Saksnr 🚩 {casenr} 🚩)</em>
|
||||||
|
<hr />
|
||||||
|
{description}
|
||||||
|
<hr />
|
||||||
|
Saken på kodedeponiet kan leses 🔗 <a href="{link}">her</a>.
|
||||||
|
issue_feature_text: |
|
||||||
|
Ny 🎫 forespørsel fra {user} ({userId}):<br /><br />
|
||||||
|
|
||||||
|
<strong>{title}</strong> <em>(Saksnr 🚩 {casenr} 🚩)</em>
|
||||||
|
<hr />
|
||||||
|
{description}
|
||||||
|
<hr />
|
||||||
|
Saken på kodedeponiet kan leses 🔗 <a href="{link}">her</a>.
|
||||||
|
issue_disclaimer_text: |
|
||||||
|
Denne saken ble videresendt fra dev bot fra [matrise serveren](https://chat.zuul.no)
|
||||||
|
|
||||||
|
Brukeren ({nick}) som opprettet saken, kan kontaktes der ({userId})
|
||||||
|
command_new_help: |
|
||||||
|
**"!Ny" Kommando hjelp:**
|
||||||
|
|
||||||
|
Skriv ny [type] "[tittel]" "[Beskrivelse]" for å opprette en ny sak.
|
||||||
|
|
||||||
|
**Eksempel:**
|
||||||
|
```!ny bug "Jeg fant en bug" "Dette er en lengre beskrivelse av buggen"```
|
||||||
|
|
||||||
|
Husk gåseøyne rundt tittel og beskrivelsen så jeg kan klart tyde og skille
|
||||||
|
de tre argumentene du trenger å oppgi for å opprette en ny sak. (Ikke bruk
|
||||||
|
gåseøyne for type sak du oppretter, bare tittel og beskrivelse).
|
||||||
|
|
||||||
|
{error}
|
||||||
command_pm_error: |
|
command_pm_error: |
|
||||||
Beklager, men du må kjøre kommandoen fra et rom hvor jeg befinner meg utenom personlige meldinger. Vennligst prøv igjen
|
Beklager, men du må kjøre kommandoen fra et rom hvor jeg befinner meg utenom personlige meldinger. Vennligst prøv igjen
|
||||||
command_pm_help: |
|
command_pm_help: |
|
||||||
Har sendt deg en PM med informasjon om meg selv og hvordan du sender kommandoer til meg
|
Har sendt deg en PM med informasjon om meg selv og hvordan du sender kommandoer til meg
|
||||||
command_pm_success: |
|
command_pm_success: |
|
||||||
Har sendt deg en PM med resultatet fra forespørselen din
|
Har sendt deg en PM med resultatet fra forespørselen din
|
||||||
|
command_success: |
|
||||||
|
Forespørselen din ble utført. {result}
|
||||||
167
devops_bot.py
167
devops_bot.py
|
|
@ -4,6 +4,7 @@ import json
|
||||||
import time
|
import time
|
||||||
import requests
|
import requests
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import shlex
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from mautrix.client import Client, InternalEventType, MembershipEventDispatcher, SyncStream
|
from mautrix.client import Client, InternalEventType, MembershipEventDispatcher, SyncStream
|
||||||
|
|
@ -20,10 +21,19 @@ class Config(BaseProxyConfig):
|
||||||
helper.copy("gitea_token")
|
helper.copy("gitea_token")
|
||||||
helper.copy("gitea_owner")
|
helper.copy("gitea_owner")
|
||||||
helper.copy("gitea_repo")
|
helper.copy("gitea_repo")
|
||||||
|
helper.copy("gitea_bug_label")
|
||||||
|
helper.copy("gitea_feature_label")
|
||||||
|
helper.copy("feature_issue_room")
|
||||||
|
helper.copy("bug_issue_room")
|
||||||
helper.copy("about_text")
|
helper.copy("about_text")
|
||||||
|
helper.copy("issue_bug_text")
|
||||||
|
helper.copy("issue_feature_text")
|
||||||
|
helper.copy("issue_disclaimer_text")
|
||||||
helper.copy("command_pm_error")
|
helper.copy("command_pm_error")
|
||||||
helper.copy("command_pm_help")
|
helper.copy("command_pm_help")
|
||||||
helper.copy("command_pm_success")
|
helper.copy("command_pm_success")
|
||||||
|
helper.copy("command_new_help")
|
||||||
|
helper.copy("command_success")
|
||||||
|
|
||||||
class DevopsBot(Plugin):
|
class DevopsBot(Plugin):
|
||||||
async def start(self) -> None:
|
async def start(self) -> None:
|
||||||
|
|
@ -44,9 +54,30 @@ class DevopsBot(Plugin):
|
||||||
def g_repo(self) -> str:
|
def g_repo(self) -> str:
|
||||||
return self.config["gitea_repo"]
|
return self.config["gitea_repo"]
|
||||||
|
|
||||||
|
def g_bug_label(self) -> int:
|
||||||
|
return self.config["gitea_bug_label"]
|
||||||
|
|
||||||
|
def g_feature_label(self) -> int:
|
||||||
|
return self.config["gitea_feature_label"]
|
||||||
|
|
||||||
|
def g_bug_room(self) -> str:
|
||||||
|
return self.config["bug_issue_room"]
|
||||||
|
|
||||||
|
def g_feature_room(self) -> str:
|
||||||
|
return self.config["feature_issue_room"]
|
||||||
|
|
||||||
def g_about(self) -> str:
|
def g_about(self) -> str:
|
||||||
return self.config["about_text"]
|
return self.config["about_text"]
|
||||||
|
|
||||||
|
def g_bug_text(self) -> str:
|
||||||
|
return self.config["issue_bug_text"]
|
||||||
|
|
||||||
|
def g_feature_text(self) -> str:
|
||||||
|
return self.config["issue_feature_text"]
|
||||||
|
|
||||||
|
def g_disclaimer(self) -> str:
|
||||||
|
return self.config["issue_disclaimer_text"]
|
||||||
|
|
||||||
def g_c_self_error(self) -> str:
|
def g_c_self_error(self) -> str:
|
||||||
return self.config["command_pm_error"]
|
return self.config["command_pm_error"]
|
||||||
|
|
||||||
|
|
@ -56,6 +87,12 @@ class DevopsBot(Plugin):
|
||||||
def g_c_self_success(self) -> str:
|
def g_c_self_success(self) -> str:
|
||||||
return self.config["command_pm_success"]
|
return self.config["command_pm_success"]
|
||||||
|
|
||||||
|
def g_c_new_help(self) -> str:
|
||||||
|
return self.config["command_new_help"]
|
||||||
|
|
||||||
|
def g_c_command_success(self) -> str:
|
||||||
|
return self.config["command_success"]
|
||||||
|
|
||||||
# Match all the registered pm rooms in pm_rooms with
|
# Match all the registered pm rooms in pm_rooms with
|
||||||
# the username and return the room id
|
# the username and return the room id
|
||||||
async def has_pm_room(self, user) -> str:
|
async def has_pm_room(self, user) -> str:
|
||||||
|
|
@ -72,7 +109,7 @@ class DevopsBot(Plugin):
|
||||||
if state not in ['open', 'closed', 'all']:
|
if state not in ['open', 'closed', 'all']:
|
||||||
raise ValueError("State må være enten 'open', 'closed', eller 'all")
|
raise ValueError("State må være enten 'open', 'closed', eller 'all")
|
||||||
|
|
||||||
params = {'state': state}
|
params = {'state': state, 'limit': 500}
|
||||||
query_string = urlencode(params)
|
query_string = urlencode(params)
|
||||||
url = f"https://{self.g_instance()}/api/v1/repos/{self.g_owner()}/{self.g_repo()}/issues?{query_string}"
|
url = f"https://{self.g_instance()}/api/v1/repos/{self.g_owner()}/{self.g_repo()}/issues?{query_string}"
|
||||||
headers = {
|
headers = {
|
||||||
|
|
@ -85,8 +122,116 @@ class DevopsBot(Plugin):
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return await response.json()
|
return await response.json()
|
||||||
|
|
||||||
|
# Post an issue to the configured repository
|
||||||
|
async def post_issue(self, label: int, title: str, body: str) -> list:
|
||||||
|
url = f"https://{self.g_instance()}/api/v1/repos/{self.g_owner()}/{self.g_repo()}/issues"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"token {self.g_token()}",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
issue_data = {
|
||||||
|
"title": title,
|
||||||
|
"body": body,
|
||||||
|
"labels": [
|
||||||
|
label
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(url, headers=headers, json=issue_data) as response:
|
||||||
|
response.raise_for_status()
|
||||||
|
return await response.json()
|
||||||
|
|
||||||
|
@command.new(name="ny", aliases=["new"])
|
||||||
|
@command.argument("args", pass_raw=True, required=False)
|
||||||
|
async def ny(self, evt: MessageEvent, args: str = None) -> None:
|
||||||
|
# We want to see the commands ran in a open room, so
|
||||||
|
# we can get a sense of it's usage
|
||||||
|
if evt.room_id in self.pm_rooms:
|
||||||
|
await evt.reply(self.g_c_self_error())
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check correct formatting of the arguments first
|
||||||
|
|
||||||
|
try:
|
||||||
|
arg_list = shlex.split(args)
|
||||||
|
except ValueError:
|
||||||
|
await evt.reply(self.g_c_new_help().format(error="Feil i formatering: Forsikre deg om at du bruker gåseøyne separat for tittel og beskrivelse"))
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(arg_list) < 3:
|
||||||
|
await evt.reply(self.g_c_new_help().format(error="Feil i formatering. Ikke nok argumenter. Bruk !ny [type] \"[Tittel]\" \"[Beskrivelse]\""))
|
||||||
|
return
|
||||||
|
if len(arg_list) > 3:
|
||||||
|
await evt.reply(self.g_c_new_help().format(error="Feil i formatering. For mange argumenter. Bruk !ny [type] \"[Tittel]\" \"[Beskrivelse]\""))
|
||||||
|
return
|
||||||
|
|
||||||
|
issue_type, title, description = arg_list
|
||||||
|
|
||||||
|
if not title.strip() or not description.strip():
|
||||||
|
await evt.reply(self.g_c_new_help().format(error="Feil i formatering. Tittel og/eller beskrivelse kan ikke være tomme verdier"))
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(title) > 250:
|
||||||
|
await evt.reply(self.g_c_new_help().format(error="Feil i formatering. Tittelen kan ikke overskride 250 tegn."))
|
||||||
|
return
|
||||||
|
|
||||||
|
if issue_type == 'bug':
|
||||||
|
label = self.g_bug_label()
|
||||||
|
elif issue_type == 'forbedring':
|
||||||
|
label = self.g_feature_label()
|
||||||
|
else:
|
||||||
|
await evt.reply(f"Noe gikk feil med parsingen av argumentet 'type' (Mottok '{issue_type}' forventet 'bug' eller 'forbedring')")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Append information about the sender to the description text before posting it
|
||||||
|
description_without_signature = description # store description in a new variable for use on matrix
|
||||||
|
|
||||||
|
description += f"\n\n--\n"
|
||||||
|
description += self.g_disclaimer().format(
|
||||||
|
nick=self.client.parse_user_id(evt.sender)[0],
|
||||||
|
userId=evt.sender
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create issue on the git repository, and make a thread on this matrix server
|
||||||
|
|
||||||
|
try:
|
||||||
|
new_issue = await self.post_issue(label=label, title=title, body=description)
|
||||||
|
if issue_type == 'bug':
|
||||||
|
await self.client.send_notice(self.g_bug_room(), html=self.g_bug_text().format(
|
||||||
|
user=self.client.parse_user_id(evt.sender)[0],
|
||||||
|
userId=evt.sender,
|
||||||
|
title=title,
|
||||||
|
description=description_without_signature,
|
||||||
|
link=new_issue["html_url"],
|
||||||
|
casenr=new_issue["number"]
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
await self.client.send_notice(self.g_feature_room(), html=self.g_feature_text().format(
|
||||||
|
user=self.client.parse_user_id(evt.sender)[0],
|
||||||
|
userId=evt.sender,
|
||||||
|
title=title,
|
||||||
|
description=description_without_signature,
|
||||||
|
link=new_issue["html_url"],
|
||||||
|
casenr=new_issue["number"]
|
||||||
|
))
|
||||||
|
|
||||||
|
await evt.reply(self.g_c_command_success().format(
|
||||||
|
result=f"Sak med saksnr {new_issue['number']} ble opprettet. Bruk !sak for å se detaljer"
|
||||||
|
))
|
||||||
|
except aiohttp.ClientError as e:
|
||||||
|
await evt.reply(f"Feil ved forespørsel: {e}")
|
||||||
|
except aiohttp.ContentTypeError as e:
|
||||||
|
await evt.reply(f"Feil ved syntaktisk analyse av JSON forespørsel: {e}")
|
||||||
|
except KeyError as e:
|
||||||
|
await evt.reply(f"En feil oppstod ved forsøk på å hente saksdata fra ny sak: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
await evt.reply(f"En uventet feil oppstod: {e}")
|
||||||
|
|
||||||
@command.new(name="saker", aliases=["cases"])
|
@command.new(name="saker", aliases=["cases"])
|
||||||
@command.argument("status", pass_raw=True, required=False)
|
@command.argument("status", required=False)
|
||||||
async def saker(self, evt: MessageEvent, status: str) -> None:
|
async def saker(self, evt: MessageEvent, status: str) -> None:
|
||||||
# We want to see the commands ran in a open room, so
|
# We want to see the commands ran in a open room, so
|
||||||
# we can get a sense of it's usage
|
# we can get a sense of it's usage
|
||||||
|
|
@ -108,16 +253,14 @@ class DevopsBot(Plugin):
|
||||||
await evt.reply(f"Ukjent status {status}. Status kan enten være 'åpen', 'lukket', eller 'alle'")
|
await evt.reply(f"Ukjent status {status}. Status kan enten være 'åpen', 'lukket', eller 'alle'")
|
||||||
return
|
return
|
||||||
|
|
||||||
output = """
|
output = "<table><tr><th>Saksnr</th><th>Tittel</th><th>Type(r)</th></tr>"
|
||||||
|Saksnr | Tittel | Type(r) |
|
|
||||||
| :--- | :----: | ---: |
|
|
||||||
"""
|
|
||||||
|
|
||||||
for issue in issues:
|
for issue in issues:
|
||||||
output += f"\n| {issue['number']} | {issue['title']} | "
|
output += f"<tr><td>{issue['number']}</td><td>{issue['title']}</td><td>"
|
||||||
for label in issue['labels']:
|
labels = " ".join(label['name'] for label in issue['labels'])
|
||||||
output += f"{label['name']} "
|
output += f"{labels}</td></tr>"
|
||||||
output += '|'
|
|
||||||
|
output += "</table>"
|
||||||
|
|
||||||
# Check if the bot already has a PM session with the user
|
# Check if the bot already has a PM session with the user
|
||||||
# so we don't stack up invites to new rooms for each
|
# so we don't stack up invites to new rooms for each
|
||||||
|
|
@ -125,11 +268,11 @@ class DevopsBot(Plugin):
|
||||||
pm_room = await self.has_pm_room(evt.sender)
|
pm_room = await self.has_pm_room(evt.sender)
|
||||||
|
|
||||||
if pm_room is not 'NONE':
|
if pm_room is not 'NONE':
|
||||||
await self.client.send_text(pm_room, output)
|
await self.client.send_text(pm_room, html=output)
|
||||||
else:
|
else:
|
||||||
pm_room = await self.client.create_room(is_direct=True, invitees=[evt.sender])
|
pm_room = await self.client.create_room(is_direct=True, invitees=[evt.sender])
|
||||||
self.pm_rooms.add(pm_room)
|
self.pm_rooms.add(pm_room)
|
||||||
await self.client.send_text(pm_room, output)
|
await self.client.send_text(pm_room, html=output)
|
||||||
|
|
||||||
await evt.reply(self.g_c_self_success())
|
await evt.reply(self.g_c_self_success())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
maubot: 0.1.0
|
maubot: 0.1.0
|
||||||
id: no.arcticsoftware.devopsbot
|
id: no.arcticsoftware.devopsbot
|
||||||
version: 0.1.23
|
version: 0.1.34
|
||||||
license: AGPL-3.0-or-later
|
license: AGPL-3.0-or-later
|
||||||
modules:
|
modules:
|
||||||
- devops_bot
|
- devops_bot
|
||||||
|
|
|
||||||
BIN
no.arcticsoftware.devopsbot-v0.1.34.mbp
Normal file
BIN
no.arcticsoftware.devopsbot-v0.1.34.mbp
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user