Skip to content

Commit 1bea957

Browse files
author
Paul Philion
committed
Refactoring new ticket handling and related tests
1 parent f708fef commit 1bea957

File tree

6 files changed

+258
-85
lines changed

6 files changed

+258
-85
lines changed

data/trackers.json

Lines changed: 172 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,172 @@
1-
{"trackers":[{"id":8,"name":"External-Comms-Intake","default_status":{"id":1,"name":"New"},"description":"Default tracker for tickets taken in from emails- needs categorization","enabled_standard_fields":["assigned_to_id","category_id","fixed_version_id","parent_issue_id","start_date","due_date","estimated_hours","done_ratio","description","priority_id"]},{"id":10,"name":"Admin","default_status":{"id":1,"name":"New"},"description":"Internal admin/nonprofit related tasks","enabled_standard_fields":["assigned_to_id","category_id","fixed_version_id","parent_issue_id","start_date","due_date","estimated_hours","done_ratio","description","priority_id"]},{"id":9,"name":"Comms","default_status":{"id":1,"name":"New"},"description":"Internal and external coordination \u0026 comms","enabled_standard_fields":["assigned_to_id","category_id","fixed_version_id","parent_issue_id","start_date","due_date","estimated_hours","done_ratio","description","priority_id"]},{"id":6,"name":"Infra-Config","default_status":{"id":1,"name":"New"},"description":"Infrastructure and network configuration and maintenance like software updates, documentation, keeping Netbox up to date.","enabled_standard_fields":["assigned_to_id","category_id","fixed_version_id","parent_issue_id","start_date","due_date","estimated_hours","done_ratio","description","priority_id"]},{"id":2,"name":"Infra-Field","default_status":{"id":1,"name":"New"},"description":"Field work. New installs, repairs, physical tasks and maintenance","enabled_standard_fields":["assigned_to_id","category_id","fixed_version_id","parent_issue_id","start_date","due_date","estimated_hours","done_ratio","description","priority_id"]},{"id":4,"name":"Software-Dev","default_status":{"id":1,"name":"New"},"description":"Software development tasks","enabled_standard_fields":["assigned_to_id","category_id","fixed_version_id","parent_issue_id","start_date","due_date","estimated_hours","done_ratio","description","priority_id"]},{"id":17,"name":"Research","default_status":{"id":1,"name":"New"},"description":"Research projects (eg with UW)","enabled_standard_fields":["assigned_to_id","category_id","fixed_version_id","parent_issue_id","start_date","due_date","estimated_hours","done_ratio","description","priority_id"]},{"id":13,"name":"Test-Reject","default_status":{"id":5,"name":"Reject"},"description":"Test tickets created by mistake that need to be closed","enabled_standard_fields":["assigned_to_id","category_id","fixed_version_id","parent_issue_id","start_date","due_date","estimated_hours","done_ratio","description","priority_id"]}]}
1+
{
2+
"trackers": [
3+
{
4+
"id": 8,
5+
"name": "External-Comms-Intake",
6+
"default_status": {
7+
"id": 1,
8+
"name": "New"
9+
},
10+
"description": "Default tracker for tickets taken in from emails- needs categorization",
11+
"enabled_standard_fields": [
12+
"assigned_to_id",
13+
"category_id",
14+
"fixed_version_id",
15+
"parent_issue_id",
16+
"start_date",
17+
"due_date",
18+
"estimated_hours",
19+
"done_ratio",
20+
"description",
21+
"priority_id"
22+
]
23+
},
24+
{
25+
"id": 10,
26+
"name": "Admin",
27+
"default_status": {
28+
"id": 1,
29+
"name": "New"
30+
},
31+
"description": "Internal admin/nonprofit related tasks",
32+
"enabled_standard_fields": [
33+
"assigned_to_id",
34+
"category_id",
35+
"fixed_version_id",
36+
"parent_issue_id",
37+
"start_date",
38+
"due_date",
39+
"estimated_hours",
40+
"done_ratio",
41+
"description",
42+
"priority_id"
43+
]
44+
},
45+
{
46+
"id": 9,
47+
"name": "Comms",
48+
"default_status": {
49+
"id": 1,
50+
"name": "New"
51+
},
52+
"description": "Internal and external coordination \u0026 comms",
53+
"enabled_standard_fields": [
54+
"assigned_to_id",
55+
"category_id",
56+
"fixed_version_id",
57+
"parent_issue_id",
58+
"start_date",
59+
"due_date",
60+
"estimated_hours",
61+
"done_ratio",
62+
"description",
63+
"priority_id"
64+
]
65+
},
66+
{
67+
"id": 6,
68+
"name": "Infra-Config",
69+
"default_status": {
70+
"id": 1,
71+
"name": "New"
72+
},
73+
"description": "Infrastructure and network configuration and maintenance like software updates, documentation, keeping Netbox up to date.",
74+
"enabled_standard_fields": [
75+
"assigned_to_id",
76+
"category_id",
77+
"fixed_version_id",
78+
"parent_issue_id",
79+
"start_date",
80+
"due_date",
81+
"estimated_hours",
82+
"done_ratio",
83+
"description",
84+
"priority_id"
85+
]
86+
},
87+
{
88+
"id": 2,
89+
"name": "Infra-Field",
90+
"default_status": {
91+
"id": 1,
92+
"name": "New"
93+
},
94+
"description": "Field work. New installs, repairs, physical tasks and maintenance",
95+
"enabled_standard_fields": [
96+
"assigned_to_id",
97+
"category_id",
98+
"fixed_version_id",
99+
"parent_issue_id",
100+
"start_date",
101+
"due_date",
102+
"estimated_hours",
103+
"done_ratio",
104+
"description",
105+
"priority_id"
106+
]
107+
},
108+
{
109+
"id": 4,
110+
"name": "Software-Dev",
111+
"default_status": {
112+
"id": 1,
113+
"name": "New"
114+
},
115+
"description": "Software development tasks",
116+
"enabled_standard_fields": [
117+
"assigned_to_id",
118+
"category_id",
119+
"fixed_version_id",
120+
"parent_issue_id",
121+
"start_date",
122+
"due_date",
123+
"estimated_hours",
124+
"done_ratio",
125+
"description",
126+
"priority_id"
127+
]
128+
},
129+
{
130+
"id": 17,
131+
"name": "Research",
132+
"default_status": {
133+
"id": 1,
134+
"name": "New"
135+
},
136+
"description": "Research projects (eg with UW)",
137+
"enabled_standard_fields": [
138+
"assigned_to_id",
139+
"category_id",
140+
"fixed_version_id",
141+
"parent_issue_id",
142+
"start_date",
143+
"due_date",
144+
"estimated_hours",
145+
"done_ratio",
146+
"description",
147+
"priority_id"
148+
]
149+
},
150+
{
151+
"id": 13,
152+
"name": "Test-Reject",
153+
"default_status": {
154+
"id": 5,
155+
"name": "Reject"
156+
},
157+
"description": "Test tickets created by mistake that need to be closed",
158+
"enabled_standard_fields": [
159+
"assigned_to_id",
160+
"category_id",
161+
"fixed_version_id",
162+
"parent_issue_id",
163+
"start_date",
164+
"due_date",
165+
"estimated_hours",
166+
"done_ratio",
167+
"description",
168+
"priority_id"
169+
]
170+
}
171+
]
172+
}

netbot/cog_tickets.py

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -459,44 +459,32 @@ async def create_new_ticket(self, ctx: discord.ApplicationContext, title:str):
459459
return
460460

461461
channel_name = ctx.channel.name
462-
text = f"Created by Discord user {ctx.user.name} -> {user.login}"
463-
message = Message(from_addr=user.mail, subject=title, to=ctx.channel.name)
462+
text = f"Created by Discord user {ctx.user.name} in channel {channel_name}"
463+
message = Message(from_addr=user.mail, subject=title, to=channel_name)
464464
message.set_note(text)
465465

466-
ticket: Ticket = None
467466
ticket_id = NetBot.parse_thread_title(channel_name)
468-
log.debug(f">>> {channel_name} --> {ticket_id}")
467+
log.debug(f">>> {channel_name} --> ticket: {ticket_id}")
469468
if ticket_id:
470469
# check if it's an epic
471470
epic = self.redmine.ticket_mgr.get(ticket_id)
472471
if epic and epic.priority.name == "EPIC":
473472
log.debug(f">>> {ticket_id} is an EPIC!")
474473
ticket = self.redmine.ticket_mgr.create(user, message, parent_issue_id=ticket_id)
475-
else:
476-
ticket = self.redmine.ticket_mgr.create(user, message)
477-
else:
478-
ticket = self.redmine.ticket_mgr.create(user, message)
474+
await self.thread(ctx, ticket.id)
475+
return
479476

480-
if ticket:
481-
# ticket created, set tracker
482-
# set tracker
483-
# TODO: search up all parents in hierarchy?
484-
tracker = self.bot.redmine.ticket_mgr.get_tracker(channel_name)
485-
if tracker:
486-
log.debug(f"found {channel_name} => {tracker}")
487-
params = {
488-
"tracker_id": str(tracker.id),
489-
"notes": f"Setting tracker based on channel name: {channel_name}"
490-
}
491-
self.redmine.ticket_mgr.update(ticket.id, params, user.login)
492-
else:
493-
log.debug(f"not tracker for {channel_name}")
494-
# create related discord thread
477+
# not in ticket thread, try tracker
478+
tracker = self.bot.tracker_for_channel(channel_name)
479+
if tracker:
480+
log.debug(f"found channel: {channel_name} => tracker: {tracker}")
481+
ticket = self.redmine.ticket_mgr.create(user, message, tracker_id=tracker.id)
495482
await self.thread(ctx, ticket.id)
496-
#ticket_link = self.bot.formatter.redmine_link(ticket)
497-
#await ctx.respond(f"Created ticket {ticket_link}")
498483
else:
499-
await ctx.respond(f"Error creating ticket with title={title}")
484+
# no parent or tracker
485+
log.debug(f"no parent ot tracker for {channel_name}")
486+
ticket = self.redmine.ticket_mgr.create(user, message)
487+
await self.thread(ctx, ticket.id)
500488

501489

502490
@ticket.command(name="notify", description="Notify collaborators on a ticket")

netbot/netbot.py

Lines changed: 32 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444

4545

4646
# utility method to get a list of (one) ticket from the title of the channel, or empty list
47-
# TODO could be moved to NetBot
4847
def default_ticket(ctx: discord.AutocompleteContext) -> list[int]:
4948
# examine the thread
5049
ticket_id = NetBot.parse_thread_title(ctx.interaction.channel.name)
@@ -343,45 +342,27 @@ def channel_for_ticket(self, ticket: Ticket) -> discord.TextChannel:
343342

344343

345344
async def remind_dusty_ticket(self, ticket: Ticket) -> None:
346-
"""
347-
Remind the correct people and channels when a ticket is dusry. ticket-1608
348-
"""
345+
"""Remind the correct people and channels when a ticket is dusty."""
349346
discord_ids = self.extract_ids_from_ticket(ticket)
350347
reminder = self.formatter.format_dusty_reminder(ticket, discord_ids)
351-
352-
# first, check the syncdata.
353-
sync = ticket.validate_sync_record()
354-
if sync and sync.channel_id > 0:
355-
thread: discord.Thread = self.bot.get_channel(sync.channel_id)
356-
if thread:
357-
await thread.send(reminder)
358-
return
359-
360-
# no syncdata, no thread. fallback to tracker-based channel
361-
channel = self.channel_for_tracker(ticket.tracker)
362-
if channel:
363-
await channel.send(reminder)
364-
return
365-
else:
366-
log.warning(f"Can't find channel for tracker={ticket.tracker.name}")
367-
368-
# shouldn't get here. this is a desperate fallback
369-
log.warning(f"No channel for ticket: {ticket.id}, {ticket.tracker} - using {FALLBACK_TEAM}")
370-
channel = self.get_channel_by_name(FALLBACK_TEAM)
371-
if channel:
372-
await channel.send(reminder)
348+
channel = self.channel_for_ticket(ticket)
349+
await channel.send(reminder)
373350

374351

375352
async def remind_dusty_tickets(self):
376-
"""Notify that tickets are about to expire.
377-
ticket-1608
378-
"""
353+
"""Notify that tickets are about to expire."""
354+
# ticket-1608
379355
# get list of tickets that will expire (based on rules in ticket_mgr)
380356
for ticket in self.redmine.ticket_mgr.dusty():
381357
await self.remind_dusty_ticket(ticket)
382358

383359

384-
def channel_for_tracker(self, tracker: NamedId):
360+
def tracker_for_channel(self, channel:str) -> NamedId:
361+
tracker_name = CHANNEL_MAPPING.get(channel, None)
362+
return self.redmine.ticket_mgr.get_tracker(tracker_name)
363+
364+
365+
def channel_for_tracker(self, tracker: NamedId) -> discord.TextChannel:
385366
for channel_name, tracker_name in CHANNEL_MAPPING.items():
386367
if tracker.name == tracker_name:
387368
return self.get_channel_by_name(channel_name)
@@ -409,26 +390,26 @@ def team_for_tracker(self, tracker: NamedId) -> Team:
409390
return None
410391

411392

412-
async def recycle_tickets(self):
413-
""" Recycle dusty tickets. From ticket-1608:
414-
After 3 weeks of inactivity, the ticket is put in "new"
415-
state and reassigned to the tracker-based group.
416-
The team members are reminded of this status change.
393+
async def recycle_ticket(self, ticket: Ticket):
394+
""" Recycle recycle an old dusty tickets.
395+
From ticket-1608: After 3 weeks of inactivity, the ticket
396+
is put in "new" state and reassigned to the tracker-based
397+
group. The team members are reminded of this status change.
417398
"""
418-
for ticket in self.redmine.ticket_mgr.recyclable():
419-
# notification to discord, or just note provided by expire?
420-
# - for now, add note to ticket with expire info, and allow sync.
421-
new_owner = self.team_for_tracker(ticket.tracker)
422-
self.redmine.ticket_mgr.recycle(ticket, new_owner)
399+
new_owner = self.team_for_tracker(ticket.tracker)
400+
self.redmine.ticket_mgr.recycle(ticket, new_owner)
423401

424-
# new_owner is a team. get the members for notification
425-
discord_ids = self.extract_ids_from_team(new_owner)
426-
log.info(f"$$$$$ team: {new_owner} {new_owner.users}")
427-
log.info(f"$$$$$ ids: {discord_ids}")
428-
reminder = self.formatter.format_recycled_reminder(ticket, discord_ids)
429-
log.info(f"$$$$$ reminder: {reminder}")
430-
channel = self.channel_for_ticket(ticket)
431-
await channel.send(reminder)
402+
# new_owner is a team. get the members for reminder
403+
discord_ids = self.discord_ids_for_team(new_owner)
404+
reminder = self.formatter.format_recycled_reminder(ticket, discord_ids)
405+
channel = self.channel_for_ticket(ticket)
406+
await channel.send(reminder)
407+
408+
409+
async def recycle_tickets(self):
410+
""" Recycle old dusty tickets."""
411+
for ticket in self.redmine.ticket_mgr.recyclable():
412+
await self.recycle_ticket(ticket)
432413

433414

434415
@commands.slash_command(name="notify", description="Force ticket notifications")
@@ -459,15 +440,15 @@ def find_ticket_thread(self, ticket_id:int) -> discord.Thread|None:
459440

460441
return None # not found
461442

462-
def extract_ids_from_team(self, team: Team) -> set[int]:
443+
444+
def discord_ids_for_team(self, team: Team) -> set[int]:
463445
"""Extract the Discord IDs from users in a team"""
464446

465447
if len(team.users) == 0:
466448
log.warning(f"TEAM with no users: {team}")
467449

468450
discord_ids = set()
469451
for named in team.users:
470-
log.info(f"--- NAMED: {named.id} {named}")
471452
user = self.redmine.user_mgr.cache.get(named.id) #expected to be cached
472453
if user:
473454
if user.discord_id:
@@ -486,6 +467,7 @@ def extract_ids_from_team(self, team: Team) -> set[int]:
486467

487468
return discord_ids
488469

470+
489471
def extract_ids_from_ticket(self, ticket: Ticket) -> set[int]:
490472
"""Extract the Discord IDs from users interested in a ticket,
491473
using owner and collaborators"""
@@ -496,8 +478,6 @@ def extract_ids_from_ticket(self, ticket: Ticket) -> set[int]:
496478
if ticket.watchers:
497479
interested.extend(ticket.watchers)
498480

499-
log.debug(f"INTERESTED: {interested}")
500-
501481
discord_ids = set()
502482
for named in interested:
503483
user = self.redmine.user_mgr.cache.get(named.id) #expected to be cached

0 commit comments

Comments
 (0)