detectkit 0.16.0__tar.gz → 0.16.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. {detectkit-0.16.0/detectkit.egg-info → detectkit-0.16.2}/PKG-INFO +1 -1
  2. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/__init__.py +1 -1
  3. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/webhook.py +25 -11
  4. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/rules/alerting.md +5 -3
  5. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/rules/cli.md +3 -2
  6. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/commands/test_alert.py +18 -2
  7. {detectkit-0.16.0 → detectkit-0.16.2/detectkit.egg-info}/PKG-INFO +1 -1
  8. {detectkit-0.16.0 → detectkit-0.16.2}/LICENSE +0 -0
  9. {detectkit-0.16.0 → detectkit-0.16.2}/MANIFEST.in +0 -0
  10. {detectkit-0.16.0 → detectkit-0.16.2}/README.md +0 -0
  11. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/__init__.py +0 -0
  12. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/__init__.py +0 -0
  13. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/base.py +0 -0
  14. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/branding.py +0 -0
  15. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/email.py +0 -0
  16. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/factory.py +0 -0
  17. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/mattermost.py +0 -0
  18. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/slack.py +0 -0
  19. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/telegram.py +0 -0
  20. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/__init__.py +0 -0
  21. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/_base.py +0 -0
  22. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
  23. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/_decision.py +0 -0
  24. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
  25. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/_recovery.py +0 -0
  26. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/_types.py +0 -0
  27. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
  28. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/__init__.py +0 -0
  29. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/_output.py +0 -0
  30. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
  31. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
  32. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
  33. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/rules/overview.md +0 -0
  34. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/rules/project.md +0 -0
  35. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
  36. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
  37. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
  38. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/commands/__init__.py +0 -0
  39. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/commands/clean.py +0 -0
  40. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/commands/init.py +0 -0
  41. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/commands/init_claude.py +0 -0
  42. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/commands/run.py +0 -0
  43. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/commands/unlock.py +0 -0
  44. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/main.py +0 -0
  45. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/config/__init__.py +0 -0
  46. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/config/metric_config.py +0 -0
  47. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/config/profile.py +0 -0
  48. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/config/project_config.py +0 -0
  49. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/config/validator.py +0 -0
  50. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/core/__init__.py +0 -0
  51. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/core/interval.py +0 -0
  52. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/core/models.py +0 -0
  53. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/__init__.py +0 -0
  54. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/_sql_manager.py +0 -0
  55. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/clickhouse_manager.py +0 -0
  56. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/__init__.py +0 -0
  57. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_alert_states.py +0 -0
  58. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_base.py +0 -0
  59. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_datapoints.py +0 -0
  60. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_detections.py +0 -0
  61. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_maintenance.py +0 -0
  62. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_metrics.py +0 -0
  63. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_schema.py +0 -0
  64. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_tasks.py +0 -0
  65. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/manager.py +0 -0
  66. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/manager.py +0 -0
  67. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/mysql_manager.py +0 -0
  68. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/postgres_manager.py +0 -0
  69. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/tables.py +0 -0
  70. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/__init__.py +0 -0
  71. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/base.py +0 -0
  72. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/factory.py +0 -0
  73. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/seasonality.py +0 -0
  74. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/statistical/__init__.py +0 -0
  75. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/statistical/_windowed.py +0 -0
  76. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/statistical/iqr.py +0 -0
  77. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/statistical/mad.py +0 -0
  78. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/statistical/manual_bounds.py +0 -0
  79. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/statistical/zscore.py +0 -0
  80. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/loaders/__init__.py +0 -0
  81. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/loaders/metric_loader.py +0 -0
  82. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/loaders/query_template.py +0 -0
  83. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/__init__.py +0 -0
  84. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/error_dispatch.py +0 -0
  85. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/task_manager/__init__.py +0 -0
  86. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
  87. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/task_manager/_base.py +0 -0
  88. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
  89. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/task_manager/_load_step.py +0 -0
  90. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/task_manager/_types.py +0 -0
  91. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/task_manager/manager.py +0 -0
  92. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/utils/__init__.py +0 -0
  93. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/utils/datetime_utils.py +0 -0
  94. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/utils/env_interpolation.py +0 -0
  95. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/utils/json_utils.py +0 -0
  96. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/utils/stats.py +0 -0
  97. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit.egg-info/SOURCES.txt +0 -0
  98. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit.egg-info/dependency_links.txt +0 -0
  99. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit.egg-info/entry_points.txt +0 -0
  100. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit.egg-info/requires.txt +0 -0
  101. {detectkit-0.16.0 → detectkit-0.16.2}/detectkit.egg-info/top_level.txt +0 -0
  102. {detectkit-0.16.0 → detectkit-0.16.2}/pyproject.toml +0 -0
  103. {detectkit-0.16.0 → detectkit-0.16.2}/requirements.txt +0 -0
  104. {detectkit-0.16.0 → detectkit-0.16.2}/setup.cfg +0 -0
  105. {detectkit-0.16.0 → detectkit-0.16.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: detectkit
3
- Version: 0.16.0
3
+ Version: 0.16.2
4
4
  Summary: Metric monitoring with automatic anomaly detection
5
5
  Author: detectkit team
6
6
  License: MIT
@@ -4,7 +4,7 @@ detectk - Anomaly Detection for Time-Series Metrics
4
4
  A Python library for data analysts and engineers to monitor metrics with automatic anomaly detection.
5
5
  """
6
6
 
7
- __version__ = "0.16.0"
7
+ __version__ = "0.16.2"
8
8
 
9
9
  from detectkit.core.interval import Interval
10
10
  from detectkit.core.models import ColumnDefinition, TableModel
@@ -293,20 +293,21 @@ class WebhookChannel(BaseAlertChannel):
293
293
  err = f"{ctx['error_type']}: {ctx['error_message']}".strip(": ")
294
294
  full("Error", code(err))
295
295
 
296
- # Optional links block (dashboard_url is already the clickable title;
297
- # surface bare URLs here too so they're visible/linkified on both).
298
- link_lines = []
296
+ # Links block kept as its own flexible field, but every entry is a
297
+ # compact clickable label, never a raw URL string. A Grafana dashboard
298
+ # URL can be a paragraph long once it carries variables; nobody should
299
+ # read that in an alert, so we hide it behind the label and render in the
300
+ # platform's link syntax. Holds dashboard + any extra links + the "how to
301
+ # read this alert" guide, joined by " · ".
302
+ link_parts = []
299
303
  if alert_data.dashboard_url:
300
- link_lines.append(f"Dashboard: {alert_data.dashboard_url}")
304
+ link_parts.append(self._link_markup(alert_data.dashboard_url, "Dashboard"))
301
305
  for label, url in alert_data.links.items():
302
- link_lines.append(f"{label}: {url}")
303
- if link_lines:
304
- full("Links", "\n".join(link_lines))
305
-
306
- # "How to read this alert" — the last field, a bare URL (auto-linkified on
307
- # both Slack and Mattermost) pointing readers at the interpretation guide.
306
+ link_parts.append(self._link_markup(url, label))
308
307
  if ctx["help_url"]:
309
- full(ctx["help_label"], ctx["help_url"])
308
+ link_parts.append(self._link_markup(ctx["help_url"], ctx["help_label"]))
309
+ if link_parts:
310
+ full("Links", " · ".join(link_parts))
310
311
 
311
312
  # A plain-text one-liner for notification previews / unsupported clients.
312
313
  if kind == "no_data":
@@ -328,6 +329,19 @@ class WebhookChannel(BaseAlertChannel):
328
329
  "mrkdwn_in": ["text", "fields"],
329
330
  }
330
331
 
332
+ def _link_markup(self, url: str, label: str) -> str:
333
+ """Render *label* as a clickable link in the target platform's syntax.
334
+
335
+ Slack incoming webhooks (``hooks.slack.com``) use ``<url|label>``;
336
+ Mattermost and other webhooks use markdown ``[label](url)``. This keeps a
337
+ link a short clickable phrase instead of a raw URL — important because a
338
+ real dashboard URL (e.g. Grafana with many variables) can be extremely
339
+ long, and no one should have to read it inside an alert.
340
+ """
341
+ if "hooks.slack.com" in self.webhook_url:
342
+ return f"<{url}|{label}>"
343
+ return f"[{label}]({url})"
344
+
331
345
  @staticmethod
332
346
  def _unix_ts(ts: Any) -> int | None:
333
347
  """Epoch seconds (UTC) for a naive-UTC timestamp, or None on failure."""
@@ -167,9 +167,11 @@ config needed. Control it project-wide with `alert_help_url` in
167
167
  Per-channel rendering (defaults only; resolved by
168
168
  `ProjectConfig.resolve_alert_help_url`):
169
169
 
170
- - **Slack / Mattermost / generic webhook** — a bottom full-width attachment field
171
- titled `How to read this alert` whose value is the bare URL (auto-linkified on
172
- both platforms).
170
+ - **Slack / Mattermost / generic webhook** — a clickable `How to read this alert`
171
+ label in the compact `Links` field (alongside `Dashboard` + any extra links),
172
+ never a raw URL. Rendered in the platform's link syntax (Slack `<url|label>`,
173
+ Mattermost/generic markdown links) so a long dashboard URL stays hidden behind
174
+ its label.
173
175
  - **Telegram** — appended to the links line (after the optional "Open dashboard"
174
176
  link) as an `<a>` link reading `How to read this alert`.
175
177
  - **Email** — in the footer, after `Sent by detectkit · <project>` (and any CC),
@@ -55,8 +55,9 @@ dtk run --select <sel> [--steps load,detect,alert] [--from DATE] [--to DATE] \
55
55
 
56
56
  Sends a mock alert (fake value/CI/severity) through the metric's configured
57
57
  channels, using that alert config's own rule (`min_detectors` / `direction` /
58
- `consecutive_anomalies`), so the preview matches a real firing. Use it to
59
- verify webhook URLs, channel permissions, and custom templates.
58
+ `consecutive_anomalies`) and the project-name `[name]` prefix, so the preview
59
+ matches a real firing. Use it to verify webhook URLs, channel permissions, and
60
+ custom templates.
60
61
 
61
62
  ## `dtk unlock --select <sel>`
62
63
 
@@ -23,6 +23,7 @@ def create_mock_alert_data(
23
23
  alerting_config,
24
24
  timezone_display: str = "UTC",
25
25
  help_url: str | None = None,
26
+ project_name: str | None = None,
26
27
  ) -> AlertData:
27
28
  """
28
29
  Create realistic mock AlertData for testing.
@@ -36,6 +37,10 @@ def create_mock_alert_data(
36
37
  timezone_display: Timezone for display
37
38
  help_url: Resolved "how to read this alert" link to preview (the
38
39
  project's ``alert_help_url``); ``None`` renders no help link.
40
+ project_name: Project name from ``detectkit_project.yml`` — stamped so
41
+ the preview carries the same ``[name]`` prefix a real ``dtk run``
42
+ renders (the run pipeline sets it in ``_alert_step.py``); ``None``
43
+ renders no prefix.
39
44
 
40
45
  Returns:
41
46
  AlertData with mock anomaly data
@@ -90,6 +95,7 @@ def create_mock_alert_data(
90
95
  },
91
96
  consecutive_count=consecutive_required,
92
97
  mentions=mentions,
98
+ project_name=project_name,
93
99
  dashboard_url=getattr(alerting_config, "dashboard_url", None),
94
100
  links=dict(getattr(alerting_config, "links", {}) or {}),
95
101
  help_url=help_url,
@@ -123,7 +129,13 @@ def run_test_alert(metric_name: str, profile: str | None = None):
123
129
  with open(project_config_path) as f:
124
130
  project_data = yaml.safe_load(f)
125
131
 
126
- metrics_dir_name = project_data.get("metrics_path", "metrics")
132
+ # Project name drives the [name] prefix a real `dtk run` stamps on every
133
+ # alert (see _alert_step.py); thread it so the preview matches a real firing.
134
+ project_name = project_data.get("name")
135
+
136
+ # Metrics dir lives under `paths.metrics` (default "metrics"); the old
137
+ # top-level `metrics_path` key is deprecated and ignored by ProjectConfig.
138
+ metrics_dir_name = (project_data.get("paths") or {}).get("metrics", "metrics")
127
139
 
128
140
  # Resolve the "how to read this alert" link so the preview matches what real
129
141
  # alerts would carry (brand default, a custom URL, or hidden via false).
@@ -187,7 +199,11 @@ def run_test_alert(metric_name: str, profile: str | None = None):
187
199
  print(f" Channels: {', '.join(alerting_config.channels)}\n")
188
200
 
189
201
  alert_data = create_mock_alert_data(
190
- metric_config, alerting_config, timezone_display, help_url=help_url
202
+ metric_config,
203
+ alerting_config,
204
+ timezone_display,
205
+ help_url=help_url,
206
+ project_name=project_name,
191
207
  )
192
208
 
193
209
  success_count = 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: detectkit
3
- Version: 0.16.0
3
+ Version: 0.16.2
4
4
  Summary: Metric monitoring with automatic anomaly detection
5
5
  Author: detectkit team
6
6
  License: MIT
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes