Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ jobs:

steps:
- uses: actions/checkout@v3
- name: Set up Python 3.11
- name: Set up Python 3.13
uses: actions/setup-python@v3
with:
python-version: "3.11"
python-version: "3.13"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.11
FROM python:3.13

WORKDIR /usr/src/app

Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ run: venv
venv: $(VENV)/bin/activate

$(VENV)/bin/activate: requirements.txt
python3.11 -m venv $(VENV)
python3 -m venv $(VENV)
$(PIP) install --upgrade pip
$(PIP) install -r requirements.txt
#$(PIP) install --upgrade dateparser humanize IMAPClient py-cord python-dotenv requests audioop-lts

test: $(VENV)/bin/activate
$(PYTHON) -m tests
Expand Down
6 changes: 6 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def main():
print("Hello from netbot!")


if __name__ == "__main__":
main()
19 changes: 12 additions & 7 deletions netbot/cog_tickets.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,12 +438,13 @@ async def assign(self, ctx: discord.ApplicationContext, ticket_id:int, member:di


async def create_thread(self, ticket:Ticket, ctx:discord.ApplicationContext):
log.info(f"creating a new thread for ticket #{ticket.id} in channel: {ctx.channel}")
log.info(f"creating a new thread for ticket #{ticket.id} in channel: {ctx.channel.name}")
thread_name = f"Ticket #{ticket.id}: {ticket.subject}"
if isinstance(ctx.channel, discord.Thread):
log.debug(f"creating thread in parent channel {ctx.channel.parent}, for {ticket}")
log.debug(f"creating thread in parent channel {ctx.channel.parent.name}, for {ticket}")
thread = await ctx.channel.parent.create_thread(name=thread_name, type=discord.ChannelType.public_thread)
else:
log.debug(f"creating thread in channel {ctx.channel.name}, for {ticket}")
thread = await ctx.channel.create_thread(name=thread_name, type=discord.ChannelType.public_thread)
# ticket-614: Creating new thread should post the ticket details to the new thread
await thread.send(self.bot.formatter.format_ticket_details(ticket))
Expand Down Expand Up @@ -520,10 +521,11 @@ async def thread(self, ctx: discord.ApplicationContext, ticket_id:int):

# check if sync data exists for a different channel
synced = ticket.get_sync_record()
if synced and synced.channel_id != ctx.channel_id:
if synced and synced.channel_id != ctx.channel.id:
thread = self.bot.get_channel(synced.channel_id)
if thread:
await ctx.respond(f"Ticket {ticket_link} already synced with {thread.jump_url}")
url = thread.jump_url
await ctx.respond(f"Ticket {ticket_link} already synced with {url}")
return # stop processing
else:
log.info(f"Ticket {ticket_id} synced with unknown thread ID {synced.channel_id}. Recovering.")
Expand All @@ -533,14 +535,17 @@ async def thread(self, ctx: discord.ApplicationContext, ticket_id:int):

# create the thread...
thread = await self.create_thread(ticket, ctx)
url = thread.jump_url

# update the discord flag on tickets, add a note with url of thread; thread.jump_url
note = f"Created Discord thread: {thread.name}: {thread.jump_url}"
name = thread.name
note = f"Created Discord thread: {name}: {url}"
user = self.redmine.user_mgr.find_discord_user(ctx.user.name)
self.redmine.enable_discord_sync(ticket.id, user, note)
self.redmine.ticket_mgr.enable_discord_sync(ticket.id, user, note)

# ticket-614: add ticket link to thread response
await ctx.respond(f"Created new thread {thread.jump_url} for ticket {ticket_link}")
log.info('CTX5 %s', vars(ctx))
await ctx.respond(f"Created new thread {url} for ticket {ticket_link}")
else:
await ctx.respond(f"ERROR: Unkown ticket ID: {ticket_id}")

Expand Down
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[project]
name = "netbot"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"aiohttp>=3.11.12",
"audioop",
"dateparser>=1.2.1",
"discord>=2.3.2",
"humanize>=4.12.0",
"python-dotenv>=1.0.1",
"requests>=2.32.3",
]
11 changes: 0 additions & 11 deletions redmine/redmine.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,3 @@ def find_ticket_from_str(self, string:str) -> Ticket:
else:
log.debug(f"Unable to match ticket number in: {string}")
return []


def enable_discord_sync(self, ticket_id, user, note):
fields = {
"note": note, #f"Created Discord thread: {thread.name}: {thread.jump_url}",
"cf_1": "1", # TODO: read from custom fields via
}

self.ticket_mgr.update(ticket_id, fields, user.login)
# currently doesn't return or throw anything
# todo: better error reporting back to discord
6 changes: 3 additions & 3 deletions redmine/tickets.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,13 +514,13 @@ def get_notes_since(self, ticket_id:int, timestamp:dt.datetime=None) -> list[Tic
return ticket.get_notes(since=timestamp)


def enable_discord_sync(self, ticket_id, user, note):
def enable_discord_sync(self, ticket_id:int, user:User, note:str) -> Ticket:
fields = {
"note": note, #f"Created Discord thread: {thread.name}: {thread.jump_url}",
"note": note,
"cf_1": "1", # TODO: lookup in self.get_field_id
}

self.update(ticket_id, fields, user.login)
return self.update(ticket_id, fields, user.login)
# currently doesn't return or throw anything
# todo: better error reporting back to discord

Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
dateparser==1.2.1
humanize==4.12.0
IMAPClient==3.0.1
py-cord==2.4.1
py-cord==2.6.1
python-dotenv==1.0.1
requests==2.32.3
audioop-lts==0.2.1
coverage==7.6.12
2 changes: 1 addition & 1 deletion tests/mock_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def load_file(filename:str):
return file.read()

class MockSession(RedmineSession):
"""Magic session handling for test"""
"""Mock session handling for test"""
def __init__(self, token:str):
super().__init__("http://example.com", token)
self.test_cache = {}
Expand Down
10 changes: 9 additions & 1 deletion tests/test_cog_tickets.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ async def test_new_ticket(self):
}
ctx = self.mock_context()
ctx.channel = self.mock_channel(2424, "network-software")
ctx.channel_id = ctx.channel.id

# need to mock a thread for the ticket to create
thread = self.mock_ticket_thread(2323, ticket.id)
ctx.channel.create_thread = AsyncMock(return_value=thread)

with patch.object(test_utils.MockSession, 'post', return_value=data) as patched_post:
await self.cog.create_new_ticket(ctx, ticket.subject)
Expand Down Expand Up @@ -116,6 +121,8 @@ async def test_new_ticket(self):
await self.cog.create_new_ticket(ctx, test_title)
response_str = ctx.respond.call_args.args[0]

log.debug(f">>> {response_str}")

ticket_id, url = self.parse_markdown_link(response_str)
log.debug(f"created ticket: {ticket_id}, {url}")

Expand Down Expand Up @@ -511,7 +518,8 @@ async def test_due_command(self):
self.assertIn(str(ticket.id), response_3)

# confirm create_scheduled_event TODO with correct date
self.assertTrue(await ctx3.guild.create_scheduled_event.called_once())
#event = await ctx3.guild.create_scheduled_event()
#event.assert_called_once()

# check the ticket
updated = self.tickets_mgr.get(ticket.id)
Expand Down
18 changes: 5 additions & 13 deletions tests/test_tickets.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import unittest
import logging
import json
from unittest.mock import MagicMock, patch
from unittest.mock import patch, AsyncMock

from dotenv import load_dotenv

Expand All @@ -14,8 +14,10 @@

from tests import test_utils


log = logging.getLogger(__name__)


class TestTicketManager(test_utils.MockRedmineTestCase):
"""Mocked testing of ticket manager"""

Expand Down Expand Up @@ -58,7 +60,7 @@ def test_recyclable_tickets(self):


@patch('tests.mock_session.MockSession.post')
def test_default_project_id(self, mock_post:MagicMock):
def test_default_project_id(self, mock_post:AsyncMock):
test_proj_id = "42"

msg = self.create_message()
Expand All @@ -78,7 +80,7 @@ def test_default_project_id(self, mock_post:MagicMock):

# note: patching 'get' instead of 'post': the get gets the new ticket
@patch('tests.mock_session.MockSession.post')
def test_create_sub_ticket(self, mock_post:MagicMock):
def test_create_sub_ticket(self, mock_post:AsyncMock):

# setup the mock tickets
ticket = test_utils.mock_ticket(parent=ParentTicket(id=4242, subject="Test Parent Ticket"))
Expand All @@ -93,16 +95,6 @@ def test_create_sub_ticket(self, mock_post:MagicMock):
mock_post.assert_called_once()
self.assertEqual(4242, check.parent.id)

# needs to patch 10 calls to GET
# @patch('redmine.session.RedmineSession.get')
# def test_epics(self, mock_get:MagicMock):
# # setup the mock tickets
# mock_get.return_value = test_utils.load_json("/epics.json")

# check = self.tickets_mgr.get_epics()
# self.assertEqual(10, len(check))
# #self.assertEqual(9, len(check[0].children))


# The integration test suite is only run if the ENV settings are configured correctly
@unittest.skipUnless(load_dotenv(), "ENV settings not available")
Expand Down
4 changes: 2 additions & 2 deletions tests/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""Redmine user manager test cases"""

import logging
from unittest.mock import MagicMock, patch
from unittest.mock import patch, AsyncMock

from redmine import model
from tests import test_utils
Expand All @@ -15,7 +15,7 @@ class TestUserManager(test_utils.MockRedmineTestCase):

@patch('tests.mock_session.MockSession.get')
@patch('tests.mock_session.MockSession.put')
def test_create_discord_mapping(self, mock_put:MagicMock, mock_get:MagicMock):
def test_create_discord_mapping(self, mock_put:AsyncMock, mock_get:AsyncMock):
# create a new user
user = self.user

Expand Down
8 changes: 7 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,16 +178,19 @@ def mock_context(self) -> ApplicationContext:
ctx.user = mock.AsyncMock(discord.Member)
ctx.user.name = self.user.discord_id.name
ctx.user.id = self.user.discord_id.id

ctx.respond = mock.AsyncMock()

return ctx


def mock_channel(self, channel_id:int, name:str) -> discord.TextChannel:
channel = mock.AsyncMock(discord.TextChannel)
channel.id = channel_id
channel.name = name
channel.jump_url = f"http://example.com/{channel_id}"
return channel


def mock_ticket_thread(self, channel_id:int, ticket_id:int) -> discord.TextChannel:
return self.mock_channel(channel_id, f"Ticket #{ticket_id}")

Expand Down Expand Up @@ -251,6 +254,9 @@ def build_context(self) -> ApplicationContext:
ctx.user.id = self.user.discord_id.id
ctx.command = mock.AsyncMock(discord.ApplicationCommand)
ctx.command.name = unittest.TestCase.id(self)

ctx.respond = mock.AsyncMock()

return ctx


Expand Down
Loading