datapipeline 3.2.0: Benachrichtigungen mit Webhook, Slack und E-Mail

This commit is contained in:
wkh
2026-10-09 22:27:00 +02:00
parent ee47baa22c
commit 3d8323a701
17 changed files with 963 additions and 120 deletions
+196 -90
View File
@@ -1,6 +1,6 @@
# DataPipeline – ETL-Framework in Ruby
Stand: 2026-10-09 · Gem `datapipeline` 3.1.0 ([CHANGELOG.md](CHANGELOG.md)) · Ruby 3.4 · Lizenz BSD-3-Clause ([LICENSE](LICENSE))
Stand: 2026-10-09 · Gem `datapipeline` 3.2.0 ([CHANGELOG.md](CHANGELOG.md)) · Ruby 3.4 · Lizenz BSD-3-Clause ([LICENSE](LICENSE))
`DataPipeline` liest Daten aus einer Quelle, schickt jede Zeile durch eine Kette von Schritten (prüfen, filtern,
umbauen, anreichern, abgleichen) und schreibt das Ergebnis in ein oder mehrere Ziele. Eine Pipeline ist ein kurzes
@@ -14,7 +14,7 @@ Festbreiten-Dateien und REST-APIs.
3. [Pipelines schreiben](#3-pipelines-schreiben) – DSL, Schritte, Prüfen, Fehler, Ergebnis
4. [Plugins](#4-plugins) – mitgelieferte Quellen und Ziele, eigene Plugins, Abhängigkeiten
5. [Satzarten-Layouts](#5-satzarten-layouts) – Dateien mit Kopf-, Positions- und Schlusssätzen
6. [Betrieb](#6-betrieb) – Kommandozeile, Verbindungen, Sperre, Abgleich, Historie, Benachrichtigung, Fehlersuche
6. [Betrieb](#6-betrieb) – Kommandozeile, Verbindungen, Sperre, Abgleich, Historie, Benachrichtigungen, Fehlersuche
7. [Umstieg](#7-umstieg) – auf 3.1, von 2.x auf 3.0 (`transform` entfernt), von 2.0 auf 2.1
8. [Entwicklung](#8-entwicklung) – Aufbau, Tests, Gem bauen
9. [Ausblick](#9-ausblick)
@@ -32,7 +32,8 @@ Festbreiten-Dateien und REST-APIs.
| Prüfen | Regeln je Spalte (Pflicht, Länge, Format, Werteliste, Typ), eigene Regeln als Block, Satzarten-Layouts; als Fehler oder nur als Warnung |
| Fehler | Abbruch, Zeile überspringen oder mit Grund in eine Fehlerdatei schreiben |
| Datenmengen | Batch im Speicher oder Streaming Zeile für Zeile mit konstantem Speicherbedarf; Postgres-Cursor und Schreiben in Paketen |
| Betrieb | Kommandozeile `etl` mit Exit-Codes, Dry-Run, Limit, inkrementelle Läufe (Wasserzeichen), Sperre gegen parallele Läufe, Upsert für wiederholbare Läufe, Summenabgleich vor dem Schreiben, Laufhistorie, Verbindungsdatei, Slack |
| Betrieb | Kommandozeile `etl` mit Exit-Codes, Dry-Run, Limit, inkrementelle Läufe (Wasserzeichen), Sperre gegen parallele Läufe, Upsert für wiederholbare Läufe, Summenabgleich vor dem Schreiben, Laufhistorie, Verbindungsdatei |
| Benachrichtigung | Berichte an Webhooks (signiert), Slack und E-Mail, je Pipeline oder je Skript; eigene Notifier |
| Dateiformate | Zeichensatz (z. B. Windows-1252) für CSV und Festbreite, BOM für CSV; Satzarten-Dateien schreiben, prüfen und lesen |
**Anwendungen:** Das Projekt `etl` nutzt das Gem für die PDS → SAP-Schnittstelle (CUDO) und für das Anlegen von
@@ -106,7 +107,7 @@ der Code hier genau der Datei entspricht.
| [2.3 Inkrementell und gestreamt](#23-inkrementell-und-gestreamt) | Wasserzeichen, Streaming |
| [2.4 Eigenes Plugin](#24-eigenes-plugin) | eine Quelle selbst schreiben |
| [2.5 REST-APIs](#25-rest-apis) | Paging, Wiederholung, Pakete, eine URL je Datensatz |
| [2.6 Produktiver Lauf](#26-produktiver-lauf) | Verbindungsdatei, Sperre, Kurzschritte, Summenabgleich, Upsert, Zeichensatz, Historie, Slack |
| [2.6 Produktiver Lauf](#26-produktiver-lauf) | Verbindungsdatei, Sperre, Kurzschritte, Summenabgleich, Upsert, Zeichensatz, Historie, Benachrichtigung |
### 2.1 Kundenimport
@@ -624,13 +625,11 @@ FileUtils.mkdir_p(out)
# Zugangsdaten stehen nicht im Skript, sondern in der Verbindungsdatei (Default: config/connections.yml)
DataPipeline.connections_file = File.expand_path('config/connections.yml', __dir__)
# Slack nur, wenn ein Token gesetzt ist; so fällt ein fehlender Token schon vor dem Lauf auf
slack = DataPipeline::Slack.new(channel: '#etl') if ENV['SLACK_TOKEN']
DataPipeline.new do
name 'buchungen_export'
lock "#{out}/.buchungen.lock" # läuft schon ein Export, bricht dieser ab (Exit-Code 3)
history "#{out}/runs.ndjson" # eine JSON-Zeile je Lauf, auch nach Abbruch
notify DataPipeline::Slack.new(channel: '#etl') if ENV['SLACK_TOKEN'] # Meldung nur bei Problemen
read container(:csv, file: "#{DATA}/buchungen.csv", csv: { col_sep: ';' })
@@ -662,10 +661,7 @@ DataPipeline.new do
write container(:csv, file: "#{out}/buchungen_sap.csv", encoding: 'Windows-1252', csv: { col_sep: ';' },
columns: %w[belnr buzei datum konto shkzg dmbtr buchungstext])
on_finish do |stats|
puts "#{stats.name}: #{stats}"
slack&.send_message("*#{stats.name}*: #{stats}") unless stats.ok?
end
on_finish { |stats| puts "#{stats.name}: #{stats}" }
end.run
```
@@ -679,7 +675,7 @@ end.run
| `totals`, `reconcile` | Summen werden exakt gerechnet; eine Abweichung bricht vor dem Schreiben ab (6.4) |
| `upsert: [belnr, buzei]` | ein wiederholter Lauf aktualisiert dieselben Zeilen, statt sie doppelt anzulegen (4.2) |
| `encoding: 'Windows-1252'` | Zeichensatz der Zieldatei (4.4) |
| `slack&.send_message` | Meldung nur bei Problemen und nur, wenn `SLACK_TOKEN` gesetzt ist (6.8) |
| `notify DataPipeline::Slack…` | Meldung nur bei Problemen und nur, wenn `SLACK_TOKEN` gesetzt ist (6.8) |
```text
INFO start
@@ -765,6 +761,7 @@ Tippfehler fallen also sofort auf.
| `on_error(:abort \| :skip \| :collect, to: storage)` | Umgang mit Fehlern in Schritten |
| `totals 'spalte', …` | Summen über Quelle und Ergebnis |
| `reconcile { \|stats\| … }` | Abgleich vor dem Schreiben; eine Meldung bricht ab |
| `notify(notifier, on: :problems)`, `notifier(:name)` | Bericht an Webhook, Slack oder E-Mail (6.8) |
| `on_finish { \|stats\| … }` | nach jedem Lauf, auch nach einem Abbruch |
| `name(str)`, `version(str)` | Name und Version des Laufs für Log, Stats und Historie |
@@ -1137,7 +1134,8 @@ Gemfile der Anwendung, die das Plugin nutzt:
| `:postgres` | `pg` |
| `:sqlite` | `sqlite3` |
| `:excel` | `roo` (lesen), `caxlsx` (schreiben) |
| `:rest`, `DataPipeline::Slack` | `http` |
| `:rest`, `DataPipeline::Slack`, `DataPipeline::Webhook` | `http` |
| `DataPipeline::Email` | `net-smtp` |
| `:csv`, `:json`, `:fixed` | – |
Eine CSV- oder JSON-Pipeline lädt keines dieser Gems. Fehlt ein Gem, meldet das Plugin bei der ersten Nutzung, welches
@@ -1299,6 +1297,7 @@ bin/etl run cudo.rb --lock=data/.cudo.lock --history=data/runs.ndjson
| `--dry-run`, `--limit=N` | für alle Pipelines des Skripts |
| `--lock=FILE` | Sperre für das ganze Skript (6.3) |
| `--history=FILE` | Laufhistorie für alle Pipelines des Skripts (6.6) |
| `--notify=NAME`, `--notify-on=WHEN` | ein Bericht für alle Pipelines des Skripts an den Notifier `NAME` aus der Verbindungsdatei; `WHEN` = `problems` (Default), `failure`, `always` (6.8) |
| `--verbose`, `--quiet` | Log-Level Debug bzw. Warnung (Default: Info) |
| jede andere `--name=wert` | `params[:name]` und unverändert an das Skript, z. B. `--out=data`, `--incremental=1` |
@@ -1423,81 +1422,181 @@ Eigene Schritte mit Nebenwirkungen (API-Aufrufe in einem `map`) fragen `dry_run?
`limit n` verarbeitet nur die ersten n Quellzeilen; im Streaming wird das Lesen dann beendet.
### 6.8 Benachrichtigung über Slack
### 6.8 Benachrichtigungen
`DataPipeline::Slack` schickt Text über die Slack-API `chat.postMessage`. Einrichten:
1. In Slack eine App anlegen (api.slack.com/apps), unter *OAuth & Permissions* den Bot-Scope `chat:write` vergeben,
die App im Workspace installieren und den *Bot User OAuth Token* (`xoxb-…`) kopieren.
2. Die App in den Ziel-Channel einladen (`/invite @appname`), sonst antwortet Slack mit `not_in_channel`.
3. Das Gem `http` ins Gemfile der Anwendung aufnehmen (4.10).
4. Den Token als Umgebungsvariable `SLACK_TOKEN` setzen oder mit `token:` übergeben; nicht ins Skript schreiben.
`notify` schickt nach einem Lauf einen Bericht an einen Notifier: einen Webhook, Slack oder E-Mail. Ein Fehler beim
Senden wird nur geloggt; Ergebnis und Exit-Code bleiben unverändert.
```ruby
slack = DataPipeline::Slack.new # Token aus ENV['SLACK_TOKEN'], Channel '#general'
slack = DataPipeline::Slack.new(channel: '#etl') # anderer Channel (Name oder ID wie 'C0123ABCD')
slack = DataPipeline::Slack.new(channel: '#etl', token: ENV.fetch('SLACK_ETL_TOKEN')) # eigener Token je Anwendung
slack.send_message('Test aus der Pipeline', verbose: true) # verbose: Antwort von Slack als JSON auf $stdout
```
| Option | Default | Bedeutung |
| --- | --- | --- |
| `token:` | `ENV['SLACK_TOKEN']` | Bot-Token; fehlt er, bricht schon `Slack.new` mit `ArgumentError` ab |
| `channel:` | `'#general'` | Ziel-Channel als Name oder ID |
`send_message(text, verbose: false)` liefert die Antwort von Slack als Hash. Meldet Slack einen Fehler (z. B.
`invalid_auth`, `channel_not_found`, `not_in_channel`), wirft die Methode eine Exception mit diesem Code. In
`on_finish` wird sie nur geloggt (`on_finish: RuntimeError: Slack#send_message failed: …`), der Lauf und sein
Exit-Code bleiben unverändert. Es gibt keine Wiederholung bei Netzwerkfehlern.
Eine ausführliche Meldung mit Status, Zählern, Laufzeit und den ersten verworfenen Zeilen. Der Notifier wird vor der
Pipeline angelegt, damit ein fehlender Token schon beim Start auffällt. Slack setzt `*fett*`, `_kursiv_` und
`` `code` `` um.
```ruby
slack = DataPipeline::Slack.new(channel: '#etl')
DataPipeline.new do
name 'cudo_export'
notify DataPipeline::Webhook.new(url: ENV['ALERT_URL'], secret: ENV['ALERT_SECRET']) # bei Problemen
notify DataPipeline::Slack.new(channel: '#etl', mention: '<!here>'), on: :failure
notify DataPipeline::Email.new(to: 'buchhaltung@example.com'), on: :always
notify notifier(:alerts) # aus der Verbindungsdatei
# … read, Schritte, write …
on_finish do |stats|
next if stats.ok? && stats.warnings.zero? # nur melden, wenn etwas zu tun ist
status = if stats.error then ':red_circle: abgebrochen'
elsif stats.rejected.positive? then ':large_orange_circle: mit verworfenen Zeilen'
else ':large_yellow_circle: mit Warnungen'
end
lines = ["*Buchungsexport #{stats.version}* #{status}#{' (Dry-Run)' if stats.dry_run}",
"Zeitraum #{params[:from]} – #{params[:till]}, Start #{stats.started_at.strftime('%d.%m.%Y %H:%M')}, " \
"Dauer #{stats.duration}s",
"gelesen #{stats.read} · geschrieben #{stats.written} · verworfen #{stats.rejected} · Warnungen #{stats.warnings}"]
lines << "Fehler: `#{stats.error.class}: #{stats.error.message}`" if stats.error
stats.errors.first(5).each do |e|
lines << "• #{e[:step]}: #{e[:error]} – Beleg #{e[:row]['belnr']}"
end
lines << "… und #{stats.rejected - 5} weitere, siehe data/fehler.csv" if stats.rejected > 5
slack.send_message(lines.join("\n"))
end
end.run
```
So sieht die Meldung in Slack aus:
| Option von `notify` | Wirkung |
| --- | --- |
| `on: :problems` (Default) | bei Abbruch, verworfenen Zeilen oder Warnungen |
| `on: :failure` | nur bei Abbruch |
| `on: :always` | nach jedem Lauf |
| `title:` | Titel des Berichts, Default der Name der Pipeline |
| `dry_run: true` | auch nach einem Dry-Run senden (Default: nicht) |
| `include_rows: true` | die verworfenen Zeilen mitsenden; Default: nur Schritt und Meldung, weil Zeilen Kundendaten enthalten können |
```text
Buchungsexport 1.0 🟠 mit verworfenen Zeilen
Zeitraum 2025-11-01 – 2025-11-30, Start 01.12.2025 06:00, Dauer 4.213s
gelesen 1250 · geschrieben 1243 · verworfen 7 · Warnungen 0
• validate#1: konto: missing – Beleg 5100000123
• validate#1: dmbtr: not decimal – Beleg 5100000124
• validate#2: Betrag 0 – Beleg 5100000130
• validate#1: shkzg: not in ["S", "H"] – Beleg 5100000131
• validate#1: konto: format /\A\d+\z/ – Beleg 5100000140
… und 2 weitere, siehe data/fehler.csv
**Bericht** – Jeder Notifier bekommt einen `DataPipeline::Report` mit den Stats eines oder mehrerer Läufe:
| Methode | Inhalt |
| --- | --- |
| `event` | `run.failed` (Abbruch), `run.rejected` (verworfene Zeilen), `run.warnings`, `run.succeeded`; bei mehreren Läufen der schlimmste |
| `title`, `status`, `subject` | z. B. `cudo_export`, `mit verworfenen Zeilen`, `cudo_export: mit verworfenen Zeilen` |
| `lines`, `to_s` | Text je Lauf: Zähler, Start, Dauer, die ersten 5 Fehler (ohne Zeilen) |
| `to_h(include_rows: false)` | JSON-Payload für Webhooks (unten) |
| `id`, `host`, `params`, `runs`, `error` | eindeutige Kennung, Rechner, Parameter der Kommandozeile, Stats, Fehler ausserhalb eines Laufs |
**Ein Bericht für das ganze Skript** – `notify` meldet jede Pipeline einzeln. Für einen Sammelbericht über alle
Pipelines eines Skripts gibt es die Kommandozeile; der Notifier kommt aus der Verbindungsdatei:
```sh
bin/etl run cudo.rb --notify=alerts # bei Problemen (Default)
bin/etl run cudo.rb --notify=alerts --notify-on=always
```
`params` gibt es im Pipeline-Block; ausserhalb heisst es `DataPipeline.params`.
Bricht das Skript selbst ab (z. B. ein Fehler vor der ersten Pipeline), meldet der Bericht `run.failed` mit diesem
Fehler. Bei belegter Sperre (Exit-Code `3`) und im Dry-Run wird nichts gesendet.
#### Webhook
`DataPipeline::Webhook` sendet den Bericht als JSON per `POST`.
| Option | Default | Bedeutung |
| --- | --- | --- |
| `url:` | – | Ziel, Pflicht |
| `secret:` | – | signiert den Body (unten) |
| `headers:` | `{}` | weitere Header, z. B. `{ 'Authorization' => "Bearer #{ENV['TOKEN']}" }` |
| `timeout:` | 10 | Sekunden je Anfrage |
| `retries:`, `retry_wait:` | 3, 2 | Wiederholung bei 429, 5xx und Netzwerkfehlern, Wartezeit `retry_wait` × Versuch; `Retry-After` wird beachtet; andere 4xx brechen sofort ab |
```json
{"schema":1,"id":"3f6c2a1e-…","event":"run.rejected","title":"cudo_export","host":"etl01",
"params":{"from":"2026-10-08"},"error":null,
"runs":[{"name":"cudo_export","version":"1.0","started_at":"2026-10-09T06:00:00.123+02:00","duration":4.213,
"read":1250,"written":1243,"rejected":7,"warnings":0,"ok":false,"error":null,
"totals":{"dmbtr":{"read":"98231.4","written":"97810.1"}},
"errors":[{"step":"validate#1","error":"konto: missing"}]}]}
```
`schema: 1` ist ein Versprechen: Felder können dazukommen, eine Änderung bestehender Felder hiesse `schema: 2`. Jede
Anfrage trägt die Header `X-DataPipeline-Event` und `X-DataPipeline-Delivery` (die `id`, bei Wiederholungen gleich;
damit erkennt der Empfänger Doppelte). Mit `secret:` kommen dazu:
```text
X-DataPipeline-Timestamp: 1791525600
X-DataPipeline-Signature: sha256=<HMAC-SHA256(secret, "<timestamp>.<body>") hexadezimal>
```
Der Empfänger prüft Signatur und Alter (gegen wiederholt eingespielte Anfragen); in Ruby z. B.:
```ruby
ok = DataPipeline::Webhook.valid_signature?(secret: ENV['ALERT_SECRET'], body: request.raw_post,
timestamp: request.headers['X-DataPipeline-Timestamp'],
signature: request.headers['X-DataPipeline-Signature'],
max_age: 300)
```
#### Slack
`DataPipeline::Slack` meldet einen Bericht als Statuszeile mit Symbol (🔴 abgebrochen, 🟠 verworfene Zeilen,
🟡 Warnungen, 🟢 ok) und darunter den Text je Lauf. `&`, `<` und `>` aus Daten und Meldungen werden maskiert.
Zwei Wege zu Slack:
| Weg | Einrichten | Aufruf |
| --- | --- | --- |
| Incoming Webhook (einfacher) | Slack-App anlegen (api.slack.com/apps), *Incoming Webhooks* aktivieren, Webhook für den Channel erzeugen | `Slack.new(webhook: ENV['SLACK_WEBHOOK'])` |
| Bot-Token | Slack-App mit Bot-Scope `chat:write` installieren, *Bot User OAuth Token* (`xoxb-…`) kopieren, App in den Channel einladen (`/invite @appname`) | `Slack.new(channel: '#etl')`, Token aus `ENV['SLACK_TOKEN']` |
| Option | Default | Bedeutung |
| --- | --- | --- |
| `webhook:` | – | URL eines Incoming Webhook; dann braucht es weder Token noch Channel |
| `token:` | `ENV['SLACK_TOKEN']` | Bot-Token; ohne Token und ohne Webhook bricht schon `Slack.new` mit `ArgumentError` ab |
| `channel:` | `'#general'` | Channel als Name oder ID (nur mit Token) |
| `mention:` | – | z. B. `'<!here>'` oder `'<!subteam^ID>'`, nur bei `run.failed` |
| `timeout:`, `retries:`, `retry_wait:` | 10, 3, 2 | wie beim Webhook |
Eigener Text geht weiter mit `send_message`, z. B. in `on_finish`:
```ruby
slack = DataPipeline::Slack.new(channel: '#etl')
slack.send_message('Monatsabschluss exportiert', verbose: true) # verbose: Antwort von Slack auf $stdout
```
Meldet Slack einen Fehler (`invalid_auth`, `channel_not_found`, `not_in_channel`), wirft der Aufruf
`DataPipeline::Slack::Error` mit diesem Code.
#### E-Mail
`DataPipeline::Email` sendet den Bericht als Text-Mail (UTF-8) über SMTP. Betreff: `[etl] <Titel>: <Status>`. Die
Zugangsdaten kommen aus der Umgebung oder als Option:
| Option | Default | Bedeutung |
| --- | --- | --- |
| `to:` | – | eine Adresse oder ein Array, Pflicht |
| `from:` | `ENV['SMTP_FROM']`, sonst `etl@<rechner>` | Absender |
| `host:`, `port:` | `ENV['SMTP_HOST']` bzw. `localhost`; `ENV['SMTP_PORT']` bzw. 587 | SMTP-Server |
| `username:`, `password:` | `ENV['SMTP_USERNAME']`, `ENV['SMTP_PASSWORD']` | Anmeldung (LOGIN); ohne Benutzer keine Anmeldung |
| `starttls:` | `:auto` | `:auto` (wenn der Server es anbietet), `:always` oder `false` (dann Default-Port 25) |
| `tls:` | `false` | `true` = TLS von Anfang an (Port 465) |
| `subject_prefix:` | `'[etl] '` | Anfang des Betreffs |
| `timeout:` | 15 | Sekunden für Verbindung und Antwort |
Mit `include_rows: true` stehen die verworfenen Zeilen als JSON am Ende der Mail. Eigener Text:
`DataPipeline::Email.new(to: …).send_mail('Betreff', "Text")`. Ein Fehler beim Senden wirft
`DataPipeline::Email::Error`. Das Plugin braucht das Gem `net-smtp`.
#### Notifier in der Verbindungsdatei
Wie Storages lassen sich Notifier in `config/connections.yml` ablegen (6.2); `type` ist `webhook`, `slack` oder
`email`, die anderen Schlüssel sind die Optionen:
```yaml
alerts:
type: webhook
url: <%= ENV['ALERT_URL'] %>
secret: <%= ENV['ALERT_SECRET'] %>
buchhaltung_mail:
type: email
to: [buchhaltung@example.com, etl@example.com]
host: smtp.example.com
username: etl@example.com
password: <%= ENV['SMTP_PASSWORD'] %>
```
```ruby
notify notifier(:buchhaltung_mail), on: :always
```
#### Eigene Notifier
Ein Notifier ist eine Klasse mit Basisklasse `DataPipeline::Notifier` und einer Methode
`deliver(report, include_rows: false)`, z. B. für Microsoft Teams oder ein Ticketsystem:
```ruby
require 'http'
class TeamsNotifier < DataPipeline::Notifier
def initialize(url:) = @url = url
def deliver report, include_rows: false
HTTP.post(@url, json: { title: report.subject, text: report.lines.join("\n\n") })
end
end
notify TeamsNotifier.new(url: ENV['TEAMS_WEBHOOK']), on: :failure
```
### 6.9 Fehlersuche
@@ -1523,11 +1622,19 @@ schnellen Blick auf das Ergebnis.
## 7. Umstieg
### 7.0 Von 3.0 auf 3.1
### 7.0 Von 3.0 auf 3.1 und 3.2
3.1 ist abwärtskompatibel. Anders als in 3.0: SQLite schreibt im Streaming (`write_row`) nicht mehr jede Zeile
einzeln, sondern je 1000 Zeilen in einer Transaktion (4.3). Wer während eines Laufs mit einer zweiten Verbindung
auf die Zieldatei schaut, sieht die Zeilen also paketweise. Das Gem steht jetzt unter der BSD-3-Clause-Lizenz.
3.1 und 3.2 sind abwärtskompatibel. Anders als in 3.0:
| Thema | bis 3.0 | jetzt | seit |
| --- | --- | --- | --- |
| SQLite, `write_row` im Streaming | jede Zeile einzeln | je 1000 Zeilen in einer Transaktion (4.3); eine zweite Verbindung sieht die Zeilen paketweise | 3.1 |
| Lizenz | nicht festgelegt | BSD-3-Clause | 3.1 |
| Slack-Fehler | `RuntimeError` | `DataPipeline::Slack::Error` (weiterhin ein `StandardError`) | 3.2 |
| `Slack.new` | lädt `http` erst beim Senden | lädt `http` sofort; Timeout 10 s und Wiederholung bei 429/5xx | 3.2 |
| `send_message(…, verbose: true)` | Antwort als eingerücktes JSON | Antwort wie von Slack geliefert | 3.2 |
Selbstgebaute Slack-Meldungen in `on_finish` laufen weiter; einfacher ist `notify` (6.8).
### 7.1 Von 2.x auf 3.0
@@ -1584,7 +1691,7 @@ ersten Zeilen sollten gleich sein.
| [lib/datapipeline/lock.rb](lib/datapipeline/lock.rb) | Sperre gegen parallele Läufe |
| [lib/datapipeline/connections.rb](lib/datapipeline/connections.rb) | Verbindungsdatei |
| [lib/datapipeline/storage/](lib/datapipeline/storage/) | `storage_plugin.rb` (Basisklasse) und je Plugin eine Datei |
| [lib/datapipeline/notification/slack.rb](lib/datapipeline/notification/slack.rb) | Slack |
| [lib/datapipeline/notification/](lib/datapipeline/notification/) | `notifier.rb` (Report, Basisklasse), `webhook.rb`, `slack.rb`, `email.rb` |
| [lib/datapipeline/version.rb](lib/datapipeline/version.rb) | `DataPipeline::VERSION` |
| [exe/etl](exe/etl) | Kommando `etl`; [bin/etl](bin/etl) startet es im Repository mit dem Bundle |
| [examples/](examples/) | Beispiele aus Kapitel 2, Daten in `examples/data/`, Verbindungsdatei in `examples/config/`, Demo-API |
@@ -1592,14 +1699,14 @@ ersten Zeilen sollten gleich sein.
### 8.2 Tests
`bundle exec rake test` startet die Minitest-Suite in [test/](test/), rund 190 Fälle. Einzelne Datei:
`bundle exec rake test` startet die Minitest-Suite in [test/](test/), rund 210 Fälle. Einzelne Datei:
`bundle exec rake test TEST=test/core/steps_test.rb`.
| Ordner | Inhalt |
| --- | --- |
| `test/plugins` | je Plugin das gemeinsame Testkit `test/support/storage_contract.rb` (Schreiben und Zurücklesen, zeilenweise, als Quelle und Ziel) plus eigene Fälle; REST gegen einen lokalen Testserver; Laden der Gems erst bei Nutzung |
| `test/plugins` | je Plugin das gemeinsame Testkit `test/support/storage_contract.rb` (Schreiben und Zurücklesen, zeilenweise, als Quelle und Ziel) plus eigene Fälle; REST, Webhook und Slack gegen einen lokalen HTTP-Testserver, E-Mail gegen einen SMTP-Testserver (`test/support/`); Laden der Gems erst bei Nutzung |
| `test/core` | DSL, Schritte und Kurzschritte, Batch und Streaming, Fehlerbehandlung, Layouts, Sperre, Verbindungen, Summen, Historie, Wasserzeichen |
| `test/cli` | `bin/etl` als Prozess: Parameter, Dry-Run, Sperre, Historie, Exit-Codes |
| `test/cli` | `bin/etl` als Prozess: Parameter, Dry-Run, Sperre, Historie, Sammelbericht `--notify`, Exit-Codes |
| `test/examples` | die Beispiele aus Kapitel 2: Code in der Doku = Datei, Lauf und Ergebnis; REST gegen die Demo-API |
Eine `.env` wird in Tests nicht geladen; kein Test erreicht eine Produktivdatenbank. Postgres-Tests legen je Test eine
@@ -1620,10 +1727,10 @@ Ein eigenes Plugin lässt sich mit `MemoryStorage` aus `test/test_helper.rb` als
- Version in [lib/datapipeline/version.rb](lib/datapipeline/version.rb), Änderungen in [CHANGELOG.md](CHANGELOG.md)
(neueste oben). Neue Funktionen erhöhen die mittlere Stelle, Breaking Changes die erste (wie 3.0, 7.1).
- Jede Version bekommt ein Tag `vX.Y.Z` (`git tag -a v3.1.0 -m "datapipeline 3.1.0" && git push origin v3.1.0`).
- Jede Version bekommt ein Tag `vX.Y.Z` (`git tag -a v3.2.0 -m "datapipeline 3.2.0" && git push origin v3.2.0`).
Anwendungen binden das Gem mit einer Versionsgrenze ein (`'~> 3.1'`, 1.2) oder fest auf ein Tag
(`tag: 'v3.1.0'`; dann geht `local.datapipeline` nicht).
- Das `Gemfile` bindet das Gem über `gemspec` ein und ergänzt die Gruppen `plugins` (`pg`, `sqlite3`, `roo`,
(`tag: 'v3.2.0'`; dann geht `local.datapipeline` nicht).
- Das `Gemfile` bindet das Gem über `gemspec` ein und ergänzt die Gruppen `plugins` (`pg`, `sqlite3`, `roo`, `net-smtp`,
`caxlsx`, `http`) und `test`; `Gemfile.lock` ist versioniert. Ohne Plugin-Gems: `bundle config set without plugins`.
- Gem bauen: `gem build datapipeline.gemspec`.
- Lizenz: BSD-3-Clause, siehe [LICENSE](LICENSE); die Datei ist Teil des Gems.
@@ -1640,6 +1747,5 @@ Offene Punkte, Reihenfolge = empfohlene Umsetzung:
schreiben), `etl list` (Pipelines, Wasserzeichen, letzter Lauf aus der Historie), `--json` für die Stats.
2. **Qualität:** CI auf git.wicom.li mit `rake test` und Postgres-Service, RuboCop, `DataPipeline::Testing` mit
`MemoryStorage` als öffentliche Testhilfe für Anwendungen.
3. **Slack:** fertige Meldung aus den Stats (`notify slack, on: :problems`), Timeout und Wiederholung bei `429`,
Maskierung von `&`, `<`, `>`, Incoming Webhooks.
3. **Benachrichtigungen:** Microsoft Teams als mitgelieferter Notifier, HTML-Mail.
4. **Weitere Plugins:** SFTP-Ablage.