tickets
(Ticket management in Noi)¶
The lino_noi.lib.tickets
plugin extends lino_xl.lib.tickets
to
make it collaborate with lino_noi.lib.working
.
In Lino Noi the site of a ticket also indicates "who is going to pay" for our work. Lino Noi uses this information when generating a service report.
This is a tested document. The following instructions are used for initialization:
>>> from lino import startup
>>> startup('lino_book.projects.noi1e.settings.demo')
>>> from lino.api.doctest import *
Tickets¶
Here is a textual description of the fields and their layout used in the detail window of a ticket.
>>> from lino.utils.diag import py2rst
>>> print(py2rst(tickets.AllTickets.detail_layout, True))
...
(main) [visible for all]:
- **General** (general_tab_1):
- **None** (overview)
- (general2):
- (general2_1): **Subscription** (order), **End user** (end_user), **Ticket type** (ticket_type)
- (triager_panel_1) [visible for developer admin]: **Project** (site) [visible for all], **Assign to** (quick_assign_to) [visible for all], **Private** (private) [visible for all]
- (general2_3): **Priority** (priority), **Planned time** (planned_time), **Deadline** (deadline)
- (general2_4): **Regular** (regular_hours), **Extra** (extra_hours), **Free** (free_hours)
- **Sessions** (working.SessionsByTicket) [visible for contributor developer admin]
- (general3): **Workflow** (workflow_buttons), **My comment** (comment), **Comments** (comments.CommentsByRFC)
- **More** (more_tab_1):
- (more1):
- (more1_1): **ID** (id), **Reference** (ref)
- **Summary** (summary)
- **Description** (description)
- (more2): **Resolution** (upgrade_notes), **Upload files** (uploads.UploadsByController) [visible for customer contributor developer admin]
- (more3):
- **State** (state)
- **Assigned to** (assigned_to)
- (more3_3): **Author** (user), **Created** (created)
- (more3_4): **Modified** (modified), **Fixed since** (fixed_since)
- **Duplicate of** (duplicate_of)
- **Duplicates** (DuplicatesByTicket)
- **Links** (links_1):
- (links_left):
- **Parent** (parent)
- **Tickets** (TicketsByParent) [visible for customer contributor developer admin]
- **Mentioned in** (comments.CommentsByMentioned)
Screenshots¶

The life cycle of a ticket¶
In Lino Noi we use the default tickets workflow defined in
lino_xl.lib.tickets.TicketStates
.
Projects¶
The list of projects in our demo database depends on who is looking at it. Anonymous users can see only public projects:
>>> rt.show(tickets.Sites)
=========== ============= ======== ================ ======== ============== ====
Reference Designation Client Contact person Remark Workflow ID
----------- ------------- -------- ---------------- -------- -------------- ----
admin admin **⚒ Active** 6
bugs bugs **⚒ Active** 3
cust cust **⚒ Active** 5
docs docs **⚒ Active** 2
pypi pypi **⚒ Active** 1
=========== ============= ======== ================ ======== ============== ====
>>> rt.login("marc").show(tickets.Sites)
=========== ============= ======== ================ ======== ================================ ====
Reference Designation Client Contact person Remark Workflow ID
----------- ------------- -------- ---------------- -------- -------------------------------- ----
admin admin **⚒ Active** → [⚹] [☉] [☾] [☑] 6
bugs bugs **⚒ Active** → [⚹] [☉] [☾] [☑] 3
cust cust **⚒ Active** 5
docs docs **⚒ Active** 2
pypi pypi **⚒ Active** 1
=========== ============= ======== ================ ======== ================================ ====
List of sites to which Jean is "subscribed" (i.e. that are assigned to a team where Jean is member):
>>> rt.login("jean").show(tickets.MySites)
======================= ============= ================================
Project Description Workflow
----------------------- ------------- --------------------------------
`pypi <Detail>`__ **⚒ Active** → [⚹] [☉] [☾] [☑]
`security <Detail>`__ **⚒ Active** → [⚹] [☉] [☾] [☑]
======================= ============= ================================
List of tickets that have not yet been assigned to a site:
>>> pv = dict(has_site=dd.YesNo.no)
>>> rt.login("robin").show(tickets.AllTickets, param_values=pv)
...
===== ============================================== ========== ====================================== =========
ID Summary Priority Workflow Project
----- ---------------------------------------------- ---------- -------------------------------------- ---------
116 Foo never bars 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
114 Default account in invoices per partner 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
112 How can I see where bar? 30 [✋] [▶] **☒ Refused**
110 Why is foo so bar 30 [✋] [▶] **☐ Ready** → [☒]
108 No more foo when bar is gone 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
106 'NoneType' object has no attribute 'isocode' 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
104 Misc optimizations in Baz 30 [✋] [▶] **☒ Refused**
102 Irritating message when bar 30 [✋] [▶] **☐ Ready** → [☒]
100 Cannot delete foo 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
98 Foo never bars 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
96 Default account in invoices per partner 30 [✋] [▶] **☒ Refused**
94 How can I see where bar? 30 [✋] [▶] **☐ Ready** → [☒]
92 Why is foo so bar 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
90 No more foo when bar is gone 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
88 'NoneType' object has no attribute 'isocode' 30 [✋] [▶] **☒ Refused**
86 Misc optimizations in Baz 30 [✋] [▶] **☐ Ready** → [☒]
84 Irritating message when bar 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
82 Cannot delete foo 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
80 Foo never bars 30 [✋] [▶] **☒ Refused**
78 Default account in invoices per partner 30 [✋] [▶] **☐ Ready** → [☒]
76 How can I see where bar? 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
74 Why is foo so bar 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
72 No more foo when bar is gone 30 [✋] [▶] **☒ Refused**
70 'NoneType' object has no attribute 'isocode' 30 [✋] [▶] **☐ Ready** → [☒]
68 Misc optimizations in Baz 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
66 Irritating message when bar 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
64 Cannot delete foo 30 [✋] [▶] **☒ Refused**
62 Foo never bars 30 [✋] [▶] **☐ Ready** → [☒]
60 Default account in invoices per partner 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
58 How can I see where bar? 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
56 Why is foo so bar 30 [✋] [▶] **☒ Refused**
54 No more foo when bar is gone 30 [✋] [▶] **☐ Ready** → [☒]
52 'NoneType' object has no attribute 'isocode' 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
50 Misc optimizations in Baz 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
48 Irritating message when bar 30 [✋] [▶] **☒ Refused**
46 Cannot delete foo 30 [✋] [▶] **☐ Ready** → [☒]
44 Foo never bars 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
42 Default account in invoices per partner 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
40 How can I see where bar? 30 [✋] [▶] **☒ Refused**
38 Why is foo so bar 30 [✋] [▶] **☐ Ready** → [☒]
36 No more foo when bar is gone 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
34 'NoneType' object has no attribute 'isocode' 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
32 Misc optimizations in Baz 30 [✋] [▶] **☒ Refused**
30 Irritating message when bar 30 [✋] [▶] **☐ Ready** → [☒]
28 Cannot delete foo 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
26 Foo never bars 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
24 Default account in invoices per partner 30 [✋] [▶] **☒ Refused**
22 How can I see where bar? 30 [✋] [▶] **☐ Ready** → [☒]
20 Why is foo so bar 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
18 No more foo when bar is gone 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
16 How to get bar from foo 30 [✋] [▶] **☒ Refused**
14 Bar cannot baz 30 [✋] [▶] **☐ Ready** → [☒]
12 Foo cannot bar 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
10 Where can I find a Foo when bazing Bazes? 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
8 Is there any Bar in Foo? 30 [✋] [▶] **☒ Refused**
6 Sell bar in baz 30 [✋] [▶] **☐ Ready** → [☒]
4 Foo and bar don't baz 30 [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
2 Bar is not always baz 30 [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
===== ============================================== ========== ====================================== =========
Ticket types¶
The demo
fixture defines the following ticket types.
>>> rt.show(tickets.TicketTypes)
============= ================== ================== ================
Designation Designation (de) Designation (fr) Reporting type
------------- ------------------ ------------------ ----------------
Bugfix Bugfix Bugfix Regular
Enhancement Enhancement Enhancement Extra
Upgrade Upgrade Upgrade Regular
============= ================== ================== ================
Deciding what to do next¶
Show all active tickets reported by me.
>>> rt.login('marc').show(tickets.MyTicketsToWork)
...
========== ====================================== ============ =========
Priority Ticket Workflow Project
---------- -------------------------------------- ------------ ---------
30 `#83 (Why is foo so bar) <Detail>`__ **☉ Open** admin
30 `#35 (Foo never bars) <Detail>`__ **☉ Open** admin
========== ====================================== ============ =========
>>> rt.login('jean').show(tickets.MyTickets)
...
========== ================================================================== ============= ============== =============================================
Priority Ticket Assigned to Planned time Workflow
---------- ------------------------------------------------------------------ ------------- -------------- ---------------------------------------------
30 `#113 (Misc optimizations in Baz) <Detail>`__ [✋] [▶] **⚹ New** → [☾] [☎] [☉] [⚒] [☐] [☑]
30 `#106 ('NoneType' object has no attribute 'isocode') <Detail>`__ [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
30 `#99 (No more foo when bar is gone) <Detail>`__ Luc [▶] **☉ Open** → [☾] [☎] [⚒] [☐] [☑] [☒]
30 `#92 (Why is foo so bar) <Detail>`__ [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
30 `#78 (Default account in invoices per partner) <Detail>`__ [✋] [▶] **☐ Ready** → [☒]
30 `#57 (Irritating message when bar) <Detail>`__ [✋] [▶] **⚹ New** → [☾] [☎] [☉] [⚒] [☐] [☑]
30 `#50 (Misc optimizations in Baz) <Detail>`__ [✋] [▶] **☎ Talk** → [☾] [☉] [☐] [☒]
30 `#43 ('NoneType' object has no attribute 'isocode') <Detail>`__ Robin Rood [▶] **☉ Open** → [☾] [☎] [⚒] [☐] [☑] [☒]
30 `#36 (No more foo when bar is gone) <Detail>`__ [✋] [▶] **⚒ Working** → [☾] [☐] [☒]
30 `#22 (How can I see where bar?) <Detail>`__ [✋] [▶] **☐ Ready** → [☒]
30 `#1 (Föö fails to bar when baz) <Detail>`__ [✋] [▶] **⚹ New** → [☾] [☎] [☉] [⚒] [☐] [☑]
========== ================================================================== ============= ============== =============================================
The backlog¶
The TicketsBySite
panel shows all the tickets for a given project. It
is a scrum backlog.
>>> pypi = tickets.Site.objects.get(ref="pypi")
>>> rt.login("robin").show(tickets.TicketsBySite, pypi)
...
==================== ================================================================= ============== ============ ======= ============ =============================================
Priority Ticket Planned time Regular Extra Free Workflow
-------------------- ----------------------------------------------------------------- -------------- ------------ ------- ------------ ---------------------------------------------
30 `#97 ('NoneType' object has no attribute 'isocode') <Detail>`__ 127:03 [✋] [▶] **⚹ New** → [☾] [☎] [☉] [⚒] [☐] [☑]
30 `#73 (Cannot delete foo) <Detail>`__ 126:53 [✋] [▶] **⚹ New** → [☾] [☎] [☉] [⚒] [☐] [☑]
30 `#49 (How can I see where bar?) <Detail>`__ 127:09 [✋] [▶] **⚹ New** → [☾] [☎] [☉] [⚒] [☐] [☑]
30 `#25 ('NoneType' object has no attribute 'isocode') <Detail>`__ 130:02 178:42 [✋] [▶] **⚹ New** → [☾] [☎] [☉] [⚒] [☐] [☑]
30 `#1 (Föö fails to bar when baz) <Detail>`__ 125:31 168:21 [✋] [▶] **⚹ New** → [☾] [☎] [☉] [⚒] [☐] [☑]
**Total (5 rows)** **636:38** **347:03**
==================== ================================================================= ============== ============ ======= ============ =============================================
Anonymous cannot see tickets of a non-public project.
>>> security = tickets.Site.objects.get(ref="security")
>>> rt.show(tickets.TicketsBySite, security)
...
No data to display
Links between tickets¶
>>> obj = tickets.Ticket.objects.get(id=20)
>>> rt.show(tickets.TicketsByParent, obj)
...
========== ==== ============================= =================
Priority ID Summary Assign to
---------- ---- ----------------------------- -----------------
30 21 Irritating message when bar luc, **romain**
========== ==== ============================= =================
Filtering tickets¶
Lino Noi modifies the list of the parameters you can use for filterings
tickets be setting a custom params_layout
.
>>> show_fields(tickets.AllTickets, all=True)
...
- Author (user) : The author or reporter of this ticket. The user who reported this
ticket to the database and is responsible for managing it.
- End user (end_user) : Only rows concerning this end user.
- Assigned_to (assigned_to) : Only tickets with this user assigned.
- Not assigned to (not_assigned_to) : Only that this user is not assigned to.
- Interesting for (interesting_for) : Only tickets interesting for this partner.
- Project (site) : Show only tickets within this project.
- Has site (has_site) : Show only (or hide) tickets which have a site assigned.
- State (state) : Only rows having this state.
- Assigned (show_assigned) : Show only (or hide) tickets that are assigned to somebody.
- Active (show_active) : Show only (or hide) tickets which are active (i.e. state is Talk
or ToDo).
- To do (show_todo) : Show only (or hide) tickets that are todo (i.e. state is New
or ToDo).
- Private (show_private) : Show only (or hide) tickets that are marked private.
- Date from (start_date) : Start of observed date range
- until (end_date) : End of observed date range
- Observed event (observed_event) :
- Has reference (has_ref) :
- Commented Last (last_commenter) : Only tickets that have this use commenting last.
- Not Commented Last (not_last_commenter) : Only tickets where this use is not the last commenter.
- Site Subscriber (subscriber) : Limit tickets to tickets that have a site this user is subscribed to.
Change observers¶
Don't read this.
>>> ar = rt.login('robin')
>>> list(tickets.Site.objects.get(ref="docs").get_change_observers())
...
[(User #6 ('Luc'), <notify.MailModes.often:often>), (User #3 ('Romain
Raffault'), <notify.MailModes.often:often>)]
>>> list(comments.Comment.objects.get(pk=1).get_change_observers())
...
[(User #7 ('Jean'), <notify.MailModes.often:often>), (User #5 ('Mathieu'),
<notify.MailModes.often:often>), (User #1 ('Robin Rood'),
<notify.MailModes.often:often>)]
>>> list(comments.Comment.objects.get(pk=155).get_change_observers())
...
[(User #4 ('Marc'), <notify.MailModes.often:often>), (User #2 ('Rolf Rompen'),
<notify.MailModes.often:often>)]