little-sister-jira
Raise a Jira issue from what little-sister is already showing: a chip beside a failing reason line, or one for the whole node, linking to Jira's own pre-filled create-issue form.
It is a surface extension, not a check type — it measures nothing and adds no
type: name. What it adds is a button in two places little-sister declares, drawn
only where an issue would make sense: not on a healthy node, not on a container's
roll-up sentence, and not twice for the same finding.
Nothing is created behind the operator's back. The chip is a plain link opened in a new tab; Jira renders its own form, the browser's Jira session decides who is filing, and nothing exists until a human presses Create. This package stores no Jira credential and holds no token.
The contract
- Requires
little-sister >= 0.3.11— a floor, never a pin. - Runs on Python 3.11 or newer — the library's floor, not a higher one of its own.
- Registers the
jiraconfiguration aspect (config/jira.yaml) and fills two template slots,reason.actionsandnode.actions. It registers no check type.
Install
# your deployment's pyproject.toml
[project]
dependencies = ["little-sister", "little-sister-jira"]
# Only while *this* one comes from git: little-sister resolves from the index.
# Delete the table once this package is on an index too — nothing else changes.
[tool.uv.sources]
little-sister-jira = { git = "…/little-sister-jira.git", tag = "v0.1.0" }
# wsgi.py — registrations first, the app last. The order is load-bearing in both
# directions: the aspect must be declared before little-sister first scans the
# configuration directory, and the slots filled before the first render.
# `isort: off` keeps an import sorter from quietly reversing that.
# isort: off
import little_sister_jira # noqa: F401 aspect + both chips
from little_sister.app import app
# isort: on
__all__ = ["app"] # without it, lint calls the app import unused
Configure
Copy examples/jira.yaml into your configuration root as
config/jira.yaml and replace the ids with your own:
base_url: https://<your-site>.atlassian.net
project_id: "10000"
issue_type_id: "10002"
Those three are required; everything else has a default. The ids come from Jira's
own API — /rest/api/3/project/<KEY>, /rest/api/3/issuetype/project?projectId=…
and /rest/api/3/field.
base_url is checked: an http(s) URL naming a host, with no query or fragment.
The example's value is deliberately not one, so copying the file unedited is refused
at startup rather than rendering a chip that points at a host which cannot resolve —
a dead button looks exactly like a working one.
| Config | Decides |
|---|---|
label |
the chip's text. Default Jira |
summary_limit |
where the summary is cut with an ellipsis. Default 200; Jira's own limit is 255 |
url_limit |
how long the whole chip URL may get. Default 6000 — see Long drafts below |
fields |
extra Jira fields by id, each a template over the tokens below — this is how a project's required custom field gets filled |
actions |
which chips are offered: reason, node, or both. Each inherits label and fields and may override either. Absent means the per-reason chip only |
A field template may interpolate {path} {name} {status} {observed}
{reason} {reasons} {summary} {url}. Substitution is plain replacement, not
str.format: a reason is captured output and routinely contains a brace. An
unknown token is left standing, so a typo shows up in the draft rather than
vanishing from it.
Nothing in that file is a credential, so no secret reference is involved. Delete it
and the chips simply stop appearing — a missing or broken config/jira.yaml
costs the button and nothing else, because a monitor that will not start because
an issue tracker is misconfigured has the priorities backwards. A configuration
directory that will not resolve is little-sister's own refusal to start, and this
package does not soften it.
What a draft looks like
The summary is <node path>: <the reason on one line>, path first, because a
Jira board shows the head of a summary and cuts the tail. The description
carries the reason whole — a fenced traceback is what the person picking the
issue up needs — plus the node path, its status, when it was last observed, and an
absolute link back to its page, since that link is read inside Jira.
The two placements differ in subject. The reason chip is about one line: its
summary and description are that line. The node chip is about the whole
condition: its summary is the node's first line and its description carries every
line.
Where both are offered, each steps aside where the other is the right subject. Where a check declares its findings as addressable members, the node chip is the file them all shortcut and stands down for a node with exactly one member, whose own chip already says the same thing. Where the reasons are prose — a failure message and its stack trace are one condition — the per-line chips stand down instead, because there is one thing to file and a chip per line would offer to raise a ticket about a traceback. Neither rule can cost you a button you cannot get back: a deployment that offers only one placement keeps it in every case, and a container declaring a single member keeps its node chip, because the per-line chip it would have deferred to is never drawn on a container.
Long drafts
Everything the chip carries travels in a GET query string, where percent-encoding
roughly triples a traceback. Forty findings under one critical band — exactly the
case the node chip exists for — reach around nine thousand characters, and a
{reasons} field template duplicates the body on top of that. Past the server's
request-line limit the operator gets a 414 from Jira instead of a form, which is
worse than no chip because the chip still looks like it works.
So the whole URL is clamped to url_limit (default 6000). What gives is the
description and the configured fields, in fair shares — a short field is not cut to
make room for a traceback that is going to be cut anyway. The project ids and the
summary are never cut. A clamped body says so and keeps its dashboard: link, which
is what makes cutting safe: the full text is one click away. The link is the last
thing given up, and it goes only when the budget cannot hold even it.
A description set in fields: is yours: it is cut like any other field, never
replaced by the stock body.
The default leaves roughly two kilobytes under the common 8 KB request-line default
(Tomcat's maxHttpHeaderSize, nginx's large_client_header_buffers) for the cookies
and headers a logged-in Jira session sends. The real ceiling belongs to your Jira
and nothing here can discover it, so confirm it and set url_limit accordingly.
Develop
little-sister is declared as a floor — the release that promised the surface
this package writes against — and it resolves from the index, like any other
dependency. There is no [tool.uv.sources] table here, and the committed
uv.lock is what a release runs against. To work against a local library
checkout, add the redirect and do not commit it: uv reads the sources table of
a dependency it resolves from a path or a checkout, so a committed line would
follow this package into every deployment that installs it.
# pyproject.toml — locally, never committed
[tool.uv.sources]
little-sister = { path = "../little-sister" }
Restore uv.lock with it. The next uv run — the pre-commit gate is one — rewrites
the lock to source = { directory = … }, so a redirect kept out of pyproject.toml
can still reach a commit through the lock beside it.
uv sync
uv run ruff check
uv run mypy
uv run mypy --python-version 3.11 # against the floor, not the interpreter you have
uv run pytest -q
# The same gate runs before every commit once the hook is enabled:
git config core.hooksPath hooks
The tests are fixture-based; nothing in this repository calls Jira. Run them with
uv run pytest, not a bare pytest: one test probes a subprocess with no
PYTHONPATH, so it only finds this package where a deployment does — installed.
Two seams are worth knowing when changing it. The chips are package data —
Jinja files inside src/little_sister_jira/templates/ — and a source checkout finds
them whether or not a wheel would, so a change there is worth verifying against a
built wheel rather than the checkout. And the chip needs no
Content-Security-Policy contribution: it is a link, not a fetch, and no directive
governs where an <a href> may point. A future chip that called Jira would need
one.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file little_sister_jira-0.1.0.tar.gz.
File metadata
- Download URL: little_sister_jira-0.1.0.tar.gz
- Upload date:
- Size: 20.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e3023939a98ac8e6b4a7ee5711ed542d102f2e93832179ca6192f0dfe34b7eae
|
|
| MD5 |
49aca63c04b70ad06f48b3faaad8fa30
|
|
| BLAKE2b-256 |
3087ad7283e217665cefe0ed47a7712f5946aefdb13fc8cc5b75f0d34ea13a6f
|
File details
Details for the file little_sister_jira-0.1.0-py3-none-any.whl.
File metadata
- Download URL: little_sister_jira-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc1a662580d869430af0a75e6a2f68fa578625da16b9a381b474f0cfa2a9ea1e
|
|
| MD5 |
aa4f58930f8382809b862eb04bd2d903
|
|
| BLAKE2b-256 |
2672f8abf6744fcfc85021307d60850a195395d64801c9b53deaa974afb95792
|