Welcome | Get started | Dive into Lino | Contribute | Topics | Reference | More
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 page is a tested document and 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):
- (general1): **None** (overview), **Add tag** (add_tag), **Tags** (topics.TagsByOwner) [visible for customer contributor developer admin]
- (general2):
- (general2_1): **Subscription** (order), **End user** (end_user), **Ticket type** (ticket_type)
- (triager_panel_1) [visible for developer admin]: **Team** (group) [visible for all], **Assign to** (quick_assign_to) [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), **Summary** (summary)
- **My nickname** (my_nickname)
- **Description** (description)
- (more2): **Reference** (ref), **Resolution** (upgrade_notes)
- (more3):
- **State** (state)
- **Assigned to** (assigned_to)
- **Author** (user)
- (more3_4): **Created** (created), **Modified** (modified)
- **Confidential** (private)
- **Upload files** (uploads.UploadsByController) [visible for customer contributor developer admin]
- **Links** (links_1):
- (links1):
- **Parent** (parent)
- **Tickets** (TicketsByParent) [visible for customer contributor developer admin]
- (links2): **Duplicate of** (duplicate_of), **Duplicates** (DuplicatesByTicket), **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 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 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 projects in which Jean is interested (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 project:
>>> pv = dict(has_site=dd.YesNo.no)
>>> rt.login("robin").show(tickets.AllTickets, param_values=pv)
...
===== =========================================== ========================================================== ======
ID Summary Workflow Team
----- ------------------------------------------- ---------------------------------------------------------- ------
116 Why is foo so bar [✋] [▶] **☒ Refused** → [⚹] [☾] [☉]
114 No more foo when bar is gone [✋] [▶] **☐ Ready** → [⚹] [☾] [☎] [☑] [☒]
112 How to get bar from foo [✋] [▶] **⚒ Working** → [⚹] [☾] [☎] [☉] [☐] [☑] [☒] [⧖]
110 Bar cannot baz [✋] [▶] **☎ Talk** → [⚹] [☾] [☉] [☐] [☑] [☒] [⧖]
108 Default account in invoices per partner [✋] [▶] **⧖ Waiting** → [⚹] [☾] [☉]
106 How can I see where bar? [✋] [▶] **☑ Closed** → [⚹] [☾] [☉]
104 Why is foo so bar [✋] [▶] **☾ Sleeping** → [⚹] [☎] [⧖]
...
12 Foo cannot bar [✋] [▶] **☉ Open** → [⚹] [☾] [☎] [☐] [☑] [☒] [⧖]
10 Where can I find a Foo when bazing Bazes? [✋] [▶] **⚹ New** → [☾] [☎] [☉] [☐] [☑] [⧖]
8 Is there any Bar in Foo? [✋] [▶] **☒ Refused** → [⚹] [☾] [☉]
6 Sell bar in baz [✋] [▶] **☐ Ready** → [⚹] [☾] [☎] [☑] [☒]
4 Foo and bar don't baz [✋] [▶] **⚒ Working** → [⚹] [☾] [☎] [☉] [☐] [☑] [☒] [⧖]
2 Bar is not always baz [✋] [▶] **☎ 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 Team
---------- ---------------------------------------------- ---------------------------------------------- ------------
30 `#101 (Foo never bars) <Detail>`__ **☎ Talk** → [⚹] [☾] [☉] [⚒] [☐] [☑] [☒] [⧖] Sales team
30 `#83 (Misc optimizations in Baz) <Detail>`__ **☎ Talk** Sales team
========== ============================================== ============================================== ============
>>> rt.login('jean').show(tickets.MyTickets)
...
========== ================================================================= ================= ==========================================================
Priority Ticket Assigned to Workflow
---------- ----------------------------------------------------------------- ----------------- ----------------------------------------------------------
30 `#92 (Why is foo so bar) <Detail>`__ [✋] [▶] **☎ Talk** → [⚹] [☾] [☉] [☐] [☑] [☒] [⧖]
30 `#85 ('NoneType' object has no attribute 'isocode') <Detail>`__ Mathieu [▶] **⚒ Working** → [⚹] [☾] [☎] [☉] [☐] [☑] [☒] [⧖]
30 `#78 (No more foo when bar is gone) <Detail>`__ [✋] [▶] **☐ Ready** → [⚹] [☾] [☎] [☑] [☒]
30 `#64 (How to get bar from foo) <Detail>`__ [✋] [▶] **⚹ New** → [☾] [☎] [☉] [☐] [☑] [⧖]
30 `#57 (Irritating message when bar) <Detail>`__ [✋] [▶] **☉ Open** → [⚹] [☾] [☎] [⚒] [☐] [☑] [☒] [⧖]
30 `#29 (Foo never bars) <Detail>`__ Rolf Rompen [▶] **☎ Talk** → [⚹] [☾] [☉] [⚒] [☐] [☑] [☒] [⧖]
30 `#22 (How can I see where bar?) <Detail>`__ [✋] [▶] **⚒ Working** → [⚹] [☾] [☎] [☉] [☐] [☑] [☒] [⧖]
30 `#15 (Bars have no foo) <Detail>`__ Romain Raffault [▶] **☐ 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 Workflow Planned time Regular Extra Free
-------------------- ------------------------------------------------------------------ ---------------------------------------------------------- -------------- ------------ ------- ------------
30 `#109 ('NoneType' object has no attribute 'isocode') <Detail>`__ [▶] **⚹ New** → [☾] [☎] [☉] [⚒] [☐] [☑] [⧖] 124:17
30 `#85 ('NoneType' object has no attribute 'isocode') <Detail>`__ [▶] **⚒ Working** → [⚹] [☾] [☎] [☉] [☐] [☑] [☒] [⧖] 122:33
30 `#73 ('NoneType' object has no attribute 'isocode') <Detail>`__ [✋] [▶] **⚹ New** → [☾] [☎] [☉] [⚒] [☐] [☑] [⧖] 126:53
30 `#49 ('NoneType' object has no attribute 'isocode') <Detail>`__ [✋] [▶] **⚒ Working** → [⚹] [☾] [☎] [☉] [☐] [☑] [☒] [⧖] 127:09
30 `#37 ('NoneType' object has no attribute 'isocode') <Detail>`__ [▶] **⚹ New** → [☾] [☎] [☉] [⚒] [☐] [☑] [⧖] 124:41 174:46
30 `#13 (Bar cannot foo) <Detail>`__ [▶] **⚒ Working** → [⚹] [☾] [☎] [☉] [☐] [☑] [☒] [⧖] 125:40 168:57
30 `#1 (Föö fails to bar when baz) <Detail>`__ [✋] [▶] **⚹ New** → [☾] [☎] [☉] [⚒] [☐] [☑] [⧖] 125:31 168:17
**Total (7 rows)** **876:44** **512:00**
==================== ================================================================== ========================================================== ============== ============ ======= ============
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.
Change observers¶
A comment is a ChangeNotifier that forwards its owner's change observers:
>>> 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>)]
>>> comments.Comment.objects.get(pk=1).owner
Group #3 ('Sales team')
>>> list(groups.Group.objects.get(id=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=1).get_change_observers())
...
[(User #4 ('Marc'), <notify.MailModes.often:often>), (User #2 ('Rolf Rompen'),
<notify.MailModes.often:often>)]
When the owner of a comment is not a ChangeNotifier, the comment has no change observers:
>>> comments.Comment.objects.get(pk=155).owner
Page #62 ('Thumbnail')
>>> list(pages.Page.objects.get(id=11).get_change_observers())
Traceback (most recent call last):
...
AttributeError: 'Page' object has no attribute 'get_change_observers'
>>> list(comments.Comment.objects.get(pk=155).get_change_observers())
...
[]
Don't read on¶
>>> print(tickets.Ticket.objects.get(pk=45))
#45 (Irritating message when bar)
>>> test_client.force_login(rt.login('robin').user)
>>> def mytest(k):
... # url = 'http://jane.mylino.net/#/api/tickets/Tickets/{}?dm=list&fmt=json&lv=1697917143.868&mjsts=1695264043.076&mk=0&pv=1&pv&pv&pv&pv&pv&pv&pv&pv&pv&pv&pv&pv&pv=24.10.2023&pv=24.10.2023&pv=10&pv&pv&pv&rp=weak-key-4&ul=en&wt=d'.format(k)
... url = 'http://jane.mylino.net/api/tickets/Tickets/{}?dm=list&fmt=json&lv=1697917143.868&mjsts=1695264043.076&mk=0&pv=1&pv&pv&pv&pv&pv&pv&pv&pv&pv&pv&pv&pv&pv=24.10.2023&pv=24.10.2023&pv=10&pv&pv&pv&rp=weak-key-4&ul=en&wt=d'.format(k)
... res = test_client.get(url)
... print(res)
>>> mytest("45")
Traceback (most recent call last):
...
lino.core.exceptions.UnresolvedChoice: Unresolved value '10' (<class 'str'>) for tickets.TicketEvents (set Site.strict_choicelist_values to False to ignore this)