Skip to content

feat: question update --db; fix Slack alerts/notifications for v0.59+#21

Merged
rohanraarora merged 2 commits into
rohanraarora:release/v0.7.0from
parikshit223933:feat/question-db-flag-and-slack-alerts
Jun 2, 2026
Merged

feat: question update --db; fix Slack alerts/notifications for v0.59+#21
rohanraarora merged 2 commits into
rohanraarora:release/v0.7.0from
parikshit223933:feat/question-db-flag-and-slack-alerts

Conversation

@parikshit223933

Copy link
Copy Markdown
Contributor

Summary

Two related gaps that together make it impossible to use the CLI for two pretty common Metabase v0.59+ workflows:

  1. Moving a saved card to a different databasequestion update lets you change name, SQL, display, viz, template tags… but not the underlying database. Setting database_id alone on PUT /api/card/{id} silently leaves dataset_query.database pointing at the old DB and the card keeps running against the original source.
  2. Setting up a Slack alertalert create --channel-type slack is rejected by v0.59+ Metabase with 400 Bad Request: {"specific-errors":{"handlers":[{"channel_type":["unknown error, received: :slack"]}]}}. Three coupled issues — wrong channel_type literal, no way to specify a Slack channel name, schedule attached to the wrong field — make Slack alerts unreachable from the CLI.

What changes

question update --db <id>

New flag on question update. Updates both the top-level database_id and dataset_query.database in one PUT, so the two never drift. Pure helper applyDatabaseToDatasetQuery covers v0.59+ stages and legacy native shapes; new tests assert both.

metabase-cli question update 24355 --db 35 --sql-file ch.sql --unsafe

Slack alerts / notifications

Three coupled fixes against /api/notification:

Bug Fix
channel_type sent as bare "slack" / "email" (rejected by v0.59+) canonicalizeChannelType() accepts bare or prefixed input and emits channel/slack / channel/email
recipients hardcoded to notification-recipient/user (can't address a Slack channel by name) New --slack-channel <name> flag emits a notification-recipient/raw-value with details.value: "#channel"
--schedule <cron> was attached to handlers[].schedule (silently ignored) Schedule now mounted at top-level subscriptions[] as notification-subscription/cron; new --schedule-type / --schedule-hour fall through to cron for legacy callers

Also wired through the renamed payload fields: alert_condition + alert_above_goalsend_condition (has_result / goal_above / goal_below), alert_first_onlysend_once. Legacy field names are still accepted as CLI input.

metabase-cli alert create \
  --card 42 \
  --condition rows \
  --channel-type slack \
  --slack-channel "#alerts" \
  --schedule "0 0 * * * ?"        # Quartz cron — hourly on the hour

Verified end-to-end against v0.59.4 (d4fb593)

Reproducing the original unknown error, received: :slack 400 on main, then running the new code against the same instance:

$ metabase-cli alert create --card 24355 --condition rows \
    --channel-type slack --slack-channel "#nsdc-assessment-alert" \
    --schedule "0 0 * * * ?"
Alert #58 created.

$ metabase-cli alert show 58
handlers: [{
  channel_type: "channel/slack",
  recipients: [{
    type: "notification-recipient/raw-value",
    details: { value: "#nsdc-assessment-alert" }
  }]
}]
subscriptions: [{
  type: "notification-subscription/cron",
  cron_schedule: "0 0 * * * ?"
}]
payload: { send_condition: "has_result", send_once: false }

And the database swap:

$ metabase-cli question update 24355 --db 35 --unsafe
Question #24355 ... updated.

$ metabase-cli question show 24355  # both fields now consistent
database_id            : 35
dataset_query.database : 35

Tests

Full suite stays green: 142 passing (138 baseline + 4 new).

  • New: applyDatabaseToDatasetQuery on v0.59+ stages and legacy native shapes; refuses to mutate input.
  • New: AlertApi.create regression that asserts channel/slack + raw-value Slack recipient + top-level notification-subscription/cron on the outgoing body.
  • Updated: AlertApi.create / AlertApi.update tests reflect the renamed send_condition / send_once fields.
Test Files  9 passed (9)
     Tests  142 passed (142)

Lint passes; build succeeds.

Back-compat

  • --channel-type slack (bare) still works — internally canonicalized to channel/slack.
  • Legacy schedule_type / schedule_hour still work — translated to Quartz cron for the new subscriptions[] shape.
  • Legacy alert_condition / alert_first_only still accepted as CLI input — mapped to send_condition / send_once inside AlertApi.
  • Existing card payloads without a --db flag are unchanged.

🤖 Generated with Claude Code

parikshit223933 and others added 2 commits May 30, 2026 01:40
Two related gaps in the CLI that make it impossible to do an in-place
database swap on a saved card, or to set up a Slack alert that the
Metabase v0.59+ notification API actually accepts.

## question update --db <id>

The CLI exposed --name, --sql, --display, etc. on `question update`, but
not a way to change the underlying database. Setting only the top-level
database_id via PUT /api/card/{id} leaves dataset_query.database
pointing at the original DB, so the card keeps running against the old
source even after the update succeeds. New flag updates both fields so
they stay consistent. Factored out applyDatabaseToDatasetQuery as a
pure helper with regression tests covering v0.59+ stages and legacy
native shapes.

## Slack alerts / notifications

Three coupled bugs that together made `--channel-type slack`
unsupported on Metabase v0.59+:

  1. channel_type was sent as the bare "slack"/"email" string. v0.59+
     requires the "channel/" prefix; the bare form fails with
     `{"specific-errors":{"handlers":[{"channel_type":
     ["unknown error, received: :slack"]}]}}`. The new
     canonicalizeChannelType helper accepts both forms and emits the
     prefixed one.

  2. recipients only supported user-id targets (always emitting
     `notification-recipient/user`). Slack channel-name targets need a
     `notification-recipient/raw-value` recipient with
     `details.value: "#channel"`. Added --slack-channel <name> on
     `alert create`, `alert update`, and `notification create`, and
     surfaced helpers (slackChannelRecipient, userRecipient,
     cronSubscription) on the API module so library users can compose
     handlers correctly too.

  3. Schedule was attached to handlers[].schedule. v0.59+ moved
     scheduling to top-level subscriptions[] as
     `notification-subscription/cron` with a cron_schedule string.
     translateChannelsToSubscriptions converts both the legacy
     schedule_type/schedule_hour cadence and a new --schedule <cron>
     flag to the new shape; the legacy form is translated to Quartz
     cron internally for back-compat.

Also renamed the payload fields the API now expects:
alert_condition + alert_above_goal -> send_condition (has_result /
goal_above / goal_below), alert_first_only -> send_once. Both legacy
field names still work as CLI input; the translation happens inside
AlertApi.

## Verified end-to-end

- `metabase-cli question update 24355 --db 35 --unsafe` now updates
  database_id AND dataset_query.database in one PUT; the card is
  immediately runnable against the new DB.
- `metabase-cli alert create --card 24355 --condition rows
  --channel-type slack --slack-channel "#nsdc-assessment-alert"
  --schedule "0 0 * * * ?"` creates alert #58 against
  v0.59.4 (d4fb593), with the v0.59+ canonical body:

      handlers: [{
        channel_type: "channel/slack",
        recipients: [{
          type: "notification-recipient/raw-value",
          details: { value: "#nsdc-assessment-alert" }
        }]
      }],
      subscriptions: [{
        type: "notification-subscription/cron",
        cron_schedule: "0 0 * * * ?"
      }],
      payload: { send_condition: "has_result", send_once: false }

## Tests

Full suite stays green: 142 passing (138 baseline + 4 new). New
coverage: applyDatabaseToDatasetQuery on v0.59+ stages and legacy
native shapes, and an AlertApi.create regression that asserts
channel/slack + raw-value recipient + top-level cron subscription on
the outgoing body.
Two review follow-ups on the v0.59+ alert work:

- alert create: canonicalize --channel-type before the Slack-recipient
  guard so the prefixed form (channel/slack) is validated the same as the
  bare form (slack). Previously the guard was skipped for the prefixed
  form and an empty-recipient Slack handler was sent.
- AlertApi.update: when only --above-goal is supplied (no --condition),
  imply a goal condition instead of falling back to rows/has_result,
  which silently downgraded a goal alert.

Adds regression tests for both, plus canonicalizeChannelType idempotency.
@rohanraarora rohanraarora changed the base branch from main to release/v0.7.0 June 2, 2026 10:59
@rohanraarora rohanraarora merged commit e6b7d1d into rohanraarora:release/v0.7.0 Jun 2, 2026
3 checks passed
@rohanraarora rohanraarora mentioned this pull request Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants