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
5 changes: 5 additions & 0 deletions docs/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ Aggregated Models
:undoc-members:
:show-inheritance:

.. autoclass:: TicketMarkdownOptions
:members:
:undoc-members:
:show-inheritance:

Common Reference Models
-----------------------

Expand Down
68 changes: 68 additions & 0 deletions docs/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,74 @@ Example output::
## Documents
- diagnostic.txt

Customising the Markdown output
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Pass a :class:`TicketMarkdownOptions` instance to select which sections
and metadata fields appear in the output. All flags default to ``True``
so the default call reproduces the full transcript shown above.

+-------------------------------+----------------------------------------------+
| Flag | Controls |
+===============================+==============================================+
| ``include_description`` | ``## Description`` section |
+-------------------------------+----------------------------------------------+
| ``include_followups`` | Followup entries in ``## Timeline`` |
+-------------------------------+----------------------------------------------+
| ``include_tasks`` | Task entries in ``## Timeline`` |
+-------------------------------+----------------------------------------------+
| ``include_solutions`` | Solution entries in ``## Timeline`` |
+-------------------------------+----------------------------------------------+
| ``include_documents`` | ``## Documents`` section |
+-------------------------------+----------------------------------------------+
| ``show_status`` | ``Status`` in the ticket subtitle |
+-------------------------------+----------------------------------------------+
| ``show_requester`` | ``Requester`` in the ticket subtitle |
+-------------------------------+----------------------------------------------+
| ``show_editor`` | ``Last edited by`` in the ticket subtitle |
+-------------------------------+----------------------------------------------+
| ``show_dates`` | All ticket-level date fields |
+-------------------------------+----------------------------------------------+
| ``show_event_author`` | ``Created by`` in event subtitles |
+-------------------------------+----------------------------------------------+
| ``show_event_editor`` | ``Last edited by`` in event subtitles |
+-------------------------------+----------------------------------------------+
| ``show_event_dates`` | All date fields in event subtitles |
+-------------------------------+----------------------------------------------+
| ``show_event_state`` | ``State`` in event subtitles |
+-------------------------------+----------------------------------------------+
| ``show_event_status`` | ``Status`` in event subtitles |
+-------------------------------+----------------------------------------------+
| ``show_duration`` | ``Duration`` in task subtitles |
+-------------------------------+----------------------------------------------+
| ``show_technician`` | ``Technician`` / ``Technician group`` |
+-------------------------------+----------------------------------------------+
| ``show_approver`` | ``Approver`` in solution subtitles |
+-------------------------------+----------------------------------------------+

Example — description and timeline only, no metadata fields:

.. code-block:: python

from glpi_python_client import TicketMarkdownOptions

opts = TicketMarkdownOptions(
include_documents=False,
show_status=False,
show_requester=False,
show_editor=False,
show_dates=False,
show_event_author=False,
show_event_editor=False,
show_event_dates=False,
show_event_state=False,
show_event_status=False,
show_duration=False,
show_technician=False,
show_approver=False,
)
print(bundle.to_markdown(opts))

Reporting helpers
~~~~~~~~~~~~~~~~~

Expand Down
2 changes: 2 additions & 0 deletions glpi_python_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
PostTicketTask,
PostTimelineDocument,
PostUser,
TicketMarkdownOptions,
)

__version__ = "0.2.1"
Expand Down Expand Up @@ -121,5 +122,6 @@
"PostTicketTask",
"PostTimelineDocument",
"PostUser",
"TicketMarkdownOptions",
"__version__",
]
6 changes: 5 additions & 1 deletion glpi_python_client/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@
PatchDocument,
PostDocument,
)
from glpi_python_client.models.custom_schema import GlpiTicketContext
from glpi_python_client.models.custom_schema import (
GlpiTicketContext,
TicketMarkdownOptions,
)

__all__ = [
"DeleteDocument",
Expand Down Expand Up @@ -136,4 +139,5 @@
"PostTicketTask",
"PostTimelineDocument",
"PostUser",
"TicketMarkdownOptions",
]
3 changes: 2 additions & 1 deletion glpi_python_client/models/custom_schema/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from glpi_python_client.models.custom_schema._ticket_context import (
GlpiTicketContext,
TicketMarkdownOptions,
)

__all__ = ["GlpiTicketContext"]
__all__ = ["GlpiTicketContext", "TicketMarkdownOptions"]
182 changes: 143 additions & 39 deletions glpi_python_client/models/custom_schema/_ticket_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any
Expand Down Expand Up @@ -90,6 +91,73 @@ def _subtitle_line(*parts: tuple[str, object | None]) -> str | None:
return f"> {' | '.join(rendered_parts)}"


@dataclass
class TicketMarkdownOptions:
"""Options controlling which sections and fields appear in the Markdown export.

All flags default to ``True`` so that a bare ``to_markdown()`` call
reproduces the original full output.

Parameters
----------
include_description : bool
Emit the ``## Description`` section with the ticket body.
include_followups : bool
Include followup entries in the ``## Timeline`` section.
include_tasks : bool
Include task entries in the ``## Timeline`` section.
include_solutions : bool
Include solution entries in the ``## Timeline`` section.
include_documents : bool
Append the ``## Documents`` section with linked file references.
show_status : bool
Emit the ``Status`` field in the ticket subtitle line.
show_requester : bool
Emit the ``Requester`` field in the ticket subtitle line.
show_editor : bool
Emit the ``Last edited by`` field in the ticket subtitle line.
show_dates : bool
Emit all ticket-level date fields (created, updated, resolved,
closed) in the ticket subtitle line.
show_event_author : bool
Emit the ``Created by`` field in timeline-entry subtitle lines.
show_event_editor : bool
Emit the ``Last edited by`` field in timeline-entry subtitle lines.
show_event_dates : bool
Emit date fields (created, updated, scheduled, planned start/end,
approved) in timeline-entry subtitle lines.
show_event_state : bool
Emit the ``State`` field in timeline-entry subtitle lines.
show_event_status : bool
Emit the ``Status`` field in timeline-entry subtitle lines.
show_duration : bool
Emit the ``Duration`` field in task subtitle lines.
show_technician : bool
Emit the ``Technician`` and ``Technician group`` fields in task
subtitle lines.
show_approver : bool
Emit the ``Approver`` field in solution subtitle lines.
"""

include_description: bool = field(default=True)
include_followups: bool = field(default=True)
include_tasks: bool = field(default=True)
include_solutions: bool = field(default=True)
include_documents: bool = field(default=True)
show_status: bool = field(default=True)
show_requester: bool = field(default=True)
show_editor: bool = field(default=True)
show_dates: bool = field(default=True)
show_event_author: bool = field(default=True)
show_event_editor: bool = field(default=True)
show_event_dates: bool = field(default=True)
show_event_state: bool = field(default=True)
show_event_status: bool = field(default=True)
show_duration: bool = field(default=True)
show_technician: bool = field(default=True)
show_approver: bool = field(default=True)


def _event_sort_key(event: Any) -> datetime:
"""Compute the sort key used to order timeline events for rendering.

Expand Down Expand Up @@ -126,7 +194,10 @@ class GlpiTicketContext(GlpiModel):
solutions: list[GetSolution] = Field(default_factory=list)
documents: list[GetTimelineDocument] = Field(default_factory=list)

def to_markdown(self) -> str:
def to_markdown(
self,
options: TicketMarkdownOptions | None = None,
) -> str:
"""Render the ticket and its timeline as one Markdown transcript.

The rendering starts with the ticket title, then a compact
Expand All @@ -140,6 +211,14 @@ def to_markdown(self) -> str:
dedicated section because the document-link payload does not
expose the same authoring metadata.

Parameters
----------
options : TicketMarkdownOptions, optional
Controls which sections and metadata fields are included in
the output. When *None* (the default) a fresh
:class:`TicketMarkdownOptions` is used, which enables all
sections and fields.

Returns
-------
str
Expand All @@ -148,6 +227,8 @@ def to_markdown(self) -> str:
never ends with trailing whitespace.
"""

opts = options if options is not None else TicketMarkdownOptions()

lines: list[str] = []
ticket = self.ticket
ticket_label = ticket.name or "(unnamed ticket)"
Expand All @@ -156,28 +237,37 @@ def to_markdown(self) -> str:
else:
lines.append(f"# Ticket \u2014 {ticket_label}")

ticket_subtitle = _subtitle_line(
("Status", ticket.status),
("Requester", ticket.user_recipient),
("Last edited by", ticket.user_editor),
("Created at", ticket.date_creation),
("Updated at", ticket.date_mod),
("Resolved at", ticket.date_solve),
("Closed at", ticket.date_close),
)
ticket_subtitle_parts: list[tuple[str, object | None]] = []
if opts.show_status:
ticket_subtitle_parts.append(("Status", ticket.status))
if opts.show_requester:
ticket_subtitle_parts.append(("Requester", ticket.user_recipient))
if opts.show_editor:
ticket_subtitle_parts.append(("Last edited by", ticket.user_editor))
if opts.show_dates:
ticket_subtitle_parts += [
("Created at", ticket.date_creation),
("Updated at", ticket.date_mod),
("Resolved at", ticket.date_solve),
("Closed at", ticket.date_close),
]
ticket_subtitle = _subtitle_line(*ticket_subtitle_parts)
if ticket_subtitle is not None:
lines.append(ticket_subtitle)

if ticket.content:
if opts.include_description and ticket.content:
lines.append("")
lines.append("## Description")
lines.append("")
lines.append(ticket.content)

events: list[tuple[str, Any]] = []
events.extend(("Followup", item) for item in self.followups)
events.extend(("Task", item) for item in self.tasks)
events.extend(("Solution", item) for item in self.solutions)
if opts.include_followups:
events.extend(("Followup", item) for item in self.followups)
if opts.include_tasks:
events.extend(("Task", item) for item in self.tasks)
if opts.include_solutions:
events.extend(("Solution", item) for item in self.solutions)
events.sort(key=lambda pair: _event_sort_key(pair[1]))

if events:
Expand All @@ -192,29 +282,43 @@ def to_markdown(self) -> str:
lines.append("")
lines.append(heading)

event_subtitle = _subtitle_line(
("Created by", getattr(event, "user", None)),
("Last edited by", getattr(event, "user_editor", None)),
("Created at", getattr(event, "date_creation", None)),
("Updated at", getattr(event, "date_mod", None)),
("Scheduled for", getattr(event, "date", None)),
("Planned start", getattr(event, "planned_begin", None)),
("Planned end", getattr(event, "planned_end", None)),
("Approved at", getattr(event, "date_approval", None)),
("State", getattr(event, "state", None)),
("Status", getattr(event, "status", None)),
(
"Duration",
(
f"{duration}s"
if (duration := getattr(event, "duration", None)) is not None
else None
),
),
("Technician", getattr(event, "user_tech", None)),
("Technician group", getattr(event, "group_tech", None)),
("Approver", getattr(event, "approver", None)),
)
event_subtitle_parts: list[tuple[str, object | None]] = []
if opts.show_event_author:
event_subtitle_parts.append(
("Created by", getattr(event, "user", None))
)
if opts.show_event_editor:
event_subtitle_parts.append(
("Last edited by", getattr(event, "user_editor", None))
)
if opts.show_event_dates:
event_subtitle_parts += [
("Created at", getattr(event, "date_creation", None)),
("Updated at", getattr(event, "date_mod", None)),
("Scheduled for", getattr(event, "date", None)),
("Planned start", getattr(event, "planned_begin", None)),
("Planned end", getattr(event, "planned_end", None)),
("Approved at", getattr(event, "date_approval", None)),
]
if opts.show_event_state:
event_subtitle_parts.append(("State", getattr(event, "state", None)))
if opts.show_event_status:
event_subtitle_parts.append(("Status", getattr(event, "status", None)))
if opts.show_duration:
duration = getattr(event, "duration", None)
event_subtitle_parts.append(
("Duration", f"{duration}s" if duration is not None else None)
)
if opts.show_technician:
event_subtitle_parts += [
("Technician", getattr(event, "user_tech", None)),
("Technician group", getattr(event, "group_tech", None)),
]
if opts.show_approver:
event_subtitle_parts.append(
("Approver", getattr(event, "approver", None))
)
event_subtitle = _subtitle_line(*event_subtitle_parts)
if event_subtitle is not None:
lines.append(event_subtitle)

Expand All @@ -223,7 +327,7 @@ def to_markdown(self) -> str:
lines.append("")
lines.append(content)

if self.documents:
if opts.include_documents and self.documents:
lines.append("")
lines.append("## Documents")
for document in self.documents:
Expand All @@ -236,4 +340,4 @@ def to_markdown(self) -> str:
return "\n".join(lines).rstrip()


__all__ = ["GlpiTicketContext"]
__all__ = ["GlpiTicketContext", "TicketMarkdownOptions"]
Loading
Loading