88
99from __future__ import annotations
1010
11+ from dataclasses import dataclass , field
1112from datetime import datetime
1213from enum import Enum
1314from typing import Any
@@ -90,6 +91,73 @@ def _subtitle_line(*parts: tuple[str, object | None]) -> str | None:
9091 return f"> { ' | ' .join (rendered_parts )} "
9192
9293
94+ @dataclass
95+ class TicketMarkdownOptions :
96+ """Options controlling which sections and fields appear in the Markdown export.
97+
98+ All flags default to ``True`` so that a bare ``to_markdown()`` call
99+ reproduces the original full output.
100+
101+ Parameters
102+ ----------
103+ include_description : bool
104+ Emit the ``## Description`` section with the ticket body.
105+ include_followups : bool
106+ Include followup entries in the ``## Timeline`` section.
107+ include_tasks : bool
108+ Include task entries in the ``## Timeline`` section.
109+ include_solutions : bool
110+ Include solution entries in the ``## Timeline`` section.
111+ include_documents : bool
112+ Append the ``## Documents`` section with linked file references.
113+ show_status : bool
114+ Emit the ``Status`` field in the ticket subtitle line.
115+ show_requester : bool
116+ Emit the ``Requester`` field in the ticket subtitle line.
117+ show_editor : bool
118+ Emit the ``Last edited by`` field in the ticket subtitle line.
119+ show_dates : bool
120+ Emit all ticket-level date fields (created, updated, resolved,
121+ closed) in the ticket subtitle line.
122+ show_event_author : bool
123+ Emit the ``Created by`` field in timeline-entry subtitle lines.
124+ show_event_editor : bool
125+ Emit the ``Last edited by`` field in timeline-entry subtitle lines.
126+ show_event_dates : bool
127+ Emit date fields (created, updated, scheduled, planned start/end,
128+ approved) in timeline-entry subtitle lines.
129+ show_event_state : bool
130+ Emit the ``State`` field in timeline-entry subtitle lines.
131+ show_event_status : bool
132+ Emit the ``Status`` field in timeline-entry subtitle lines.
133+ show_duration : bool
134+ Emit the ``Duration`` field in task subtitle lines.
135+ show_technician : bool
136+ Emit the ``Technician`` and ``Technician group`` fields in task
137+ subtitle lines.
138+ show_approver : bool
139+ Emit the ``Approver`` field in solution subtitle lines.
140+ """
141+
142+ include_description : bool = field (default = True )
143+ include_followups : bool = field (default = True )
144+ include_tasks : bool = field (default = True )
145+ include_solutions : bool = field (default = True )
146+ include_documents : bool = field (default = True )
147+ show_status : bool = field (default = True )
148+ show_requester : bool = field (default = True )
149+ show_editor : bool = field (default = True )
150+ show_dates : bool = field (default = True )
151+ show_event_author : bool = field (default = True )
152+ show_event_editor : bool = field (default = True )
153+ show_event_dates : bool = field (default = True )
154+ show_event_state : bool = field (default = True )
155+ show_event_status : bool = field (default = True )
156+ show_duration : bool = field (default = True )
157+ show_technician : bool = field (default = True )
158+ show_approver : bool = field (default = True )
159+
160+
93161def _event_sort_key (event : Any ) -> datetime :
94162 """Compute the sort key used to order timeline events for rendering.
95163
@@ -126,7 +194,10 @@ class GlpiTicketContext(GlpiModel):
126194 solutions : list [GetSolution ] = Field (default_factory = list )
127195 documents : list [GetTimelineDocument ] = Field (default_factory = list )
128196
129- def to_markdown (self ) -> str :
197+ def to_markdown (
198+ self ,
199+ options : TicketMarkdownOptions | None = None ,
200+ ) -> str :
130201 """Render the ticket and its timeline as one Markdown transcript.
131202
132203 The rendering starts with the ticket title, then a compact
@@ -140,6 +211,14 @@ def to_markdown(self) -> str:
140211 dedicated section because the document-link payload does not
141212 expose the same authoring metadata.
142213
214+ Parameters
215+ ----------
216+ options : TicketMarkdownOptions, optional
217+ Controls which sections and metadata fields are included in
218+ the output. When *None* (the default) a fresh
219+ :class:`TicketMarkdownOptions` is used, which enables all
220+ sections and fields.
221+
143222 Returns
144223 -------
145224 str
@@ -148,6 +227,8 @@ def to_markdown(self) -> str:
148227 never ends with trailing whitespace.
149228 """
150229
230+ opts = options if options is not None else TicketMarkdownOptions ()
231+
151232 lines : list [str ] = []
152233 ticket = self .ticket
153234 ticket_label = ticket .name or "(unnamed ticket)"
@@ -156,28 +237,37 @@ def to_markdown(self) -> str:
156237 else :
157238 lines .append (f"# Ticket \u2014 { ticket_label } " )
158239
159- ticket_subtitle = _subtitle_line (
160- ("Status" , ticket .status ),
161- ("Requester" , ticket .user_recipient ),
162- ("Last edited by" , ticket .user_editor ),
163- ("Created at" , ticket .date_creation ),
164- ("Updated at" , ticket .date_mod ),
165- ("Resolved at" , ticket .date_solve ),
166- ("Closed at" , ticket .date_close ),
167- )
240+ ticket_subtitle_parts : list [tuple [str , object | None ]] = []
241+ if opts .show_status :
242+ ticket_subtitle_parts .append (("Status" , ticket .status ))
243+ if opts .show_requester :
244+ ticket_subtitle_parts .append (("Requester" , ticket .user_recipient ))
245+ if opts .show_editor :
246+ ticket_subtitle_parts .append (("Last edited by" , ticket .user_editor ))
247+ if opts .show_dates :
248+ ticket_subtitle_parts += [
249+ ("Created at" , ticket .date_creation ),
250+ ("Updated at" , ticket .date_mod ),
251+ ("Resolved at" , ticket .date_solve ),
252+ ("Closed at" , ticket .date_close ),
253+ ]
254+ ticket_subtitle = _subtitle_line (* ticket_subtitle_parts )
168255 if ticket_subtitle is not None :
169256 lines .append (ticket_subtitle )
170257
171- if ticket .content :
258+ if opts . include_description and ticket .content :
172259 lines .append ("" )
173260 lines .append ("## Description" )
174261 lines .append ("" )
175262 lines .append (ticket .content )
176263
177264 events : list [tuple [str , Any ]] = []
178- events .extend (("Followup" , item ) for item in self .followups )
179- events .extend (("Task" , item ) for item in self .tasks )
180- events .extend (("Solution" , item ) for item in self .solutions )
265+ if opts .include_followups :
266+ events .extend (("Followup" , item ) for item in self .followups )
267+ if opts .include_tasks :
268+ events .extend (("Task" , item ) for item in self .tasks )
269+ if opts .include_solutions :
270+ events .extend (("Solution" , item ) for item in self .solutions )
181271 events .sort (key = lambda pair : _event_sort_key (pair [1 ]))
182272
183273 if events :
@@ -192,29 +282,43 @@ def to_markdown(self) -> str:
192282 lines .append ("" )
193283 lines .append (heading )
194284
195- event_subtitle = _subtitle_line (
196- ("Created by" , getattr (event , "user" , None )),
197- ("Last edited by" , getattr (event , "user_editor" , None )),
198- ("Created at" , getattr (event , "date_creation" , None )),
199- ("Updated at" , getattr (event , "date_mod" , None )),
200- ("Scheduled for" , getattr (event , "date" , None )),
201- ("Planned start" , getattr (event , "planned_begin" , None )),
202- ("Planned end" , getattr (event , "planned_end" , None )),
203- ("Approved at" , getattr (event , "date_approval" , None )),
204- ("State" , getattr (event , "state" , None )),
205- ("Status" , getattr (event , "status" , None )),
206- (
207- "Duration" ,
208- (
209- f"{ duration } s"
210- if (duration := getattr (event , "duration" , None )) is not None
211- else None
212- ),
213- ),
214- ("Technician" , getattr (event , "user_tech" , None )),
215- ("Technician group" , getattr (event , "group_tech" , None )),
216- ("Approver" , getattr (event , "approver" , None )),
217- )
285+ event_subtitle_parts : list [tuple [str , object | None ]] = []
286+ if opts .show_event_author :
287+ event_subtitle_parts .append (
288+ ("Created by" , getattr (event , "user" , None ))
289+ )
290+ if opts .show_event_editor :
291+ event_subtitle_parts .append (
292+ ("Last edited by" , getattr (event , "user_editor" , None ))
293+ )
294+ if opts .show_event_dates :
295+ event_subtitle_parts += [
296+ ("Created at" , getattr (event , "date_creation" , None )),
297+ ("Updated at" , getattr (event , "date_mod" , None )),
298+ ("Scheduled for" , getattr (event , "date" , None )),
299+ ("Planned start" , getattr (event , "planned_begin" , None )),
300+ ("Planned end" , getattr (event , "planned_end" , None )),
301+ ("Approved at" , getattr (event , "date_approval" , None )),
302+ ]
303+ if opts .show_event_state :
304+ event_subtitle_parts .append (("State" , getattr (event , "state" , None )))
305+ if opts .show_event_status :
306+ event_subtitle_parts .append (("Status" , getattr (event , "status" , None )))
307+ if opts .show_duration :
308+ duration = getattr (event , "duration" , None )
309+ event_subtitle_parts .append (
310+ ("Duration" , f"{ duration } s" if duration is not None else None )
311+ )
312+ if opts .show_technician :
313+ event_subtitle_parts += [
314+ ("Technician" , getattr (event , "user_tech" , None )),
315+ ("Technician group" , getattr (event , "group_tech" , None )),
316+ ]
317+ if opts .show_approver :
318+ event_subtitle_parts .append (
319+ ("Approver" , getattr (event , "approver" , None ))
320+ )
321+ event_subtitle = _subtitle_line (* event_subtitle_parts )
218322 if event_subtitle is not None :
219323 lines .append (event_subtitle )
220324
@@ -223,7 +327,7 @@ def to_markdown(self) -> str:
223327 lines .append ("" )
224328 lines .append (content )
225329
226- if self .documents :
330+ if opts . include_documents and self .documents :
227331 lines .append ("" )
228332 lines .append ("## Documents" )
229333 for document in self .documents :
@@ -236,4 +340,4 @@ def to_markdown(self) -> str:
236340 return "\n " .join (lines ).rstrip ()
237341
238342
239- __all__ = ["GlpiTicketContext" ]
343+ __all__ = ["GlpiTicketContext" , "TicketMarkdownOptions" ]
0 commit comments