Skip to content

feat: add Scripts — run arbitrary parameterized code from the UI - #27

Merged
rogercampos merged 1 commit into
mainfrom
feature/scripts
Jul 23, 2026
Merged

feat: add Scripts — run arbitrary parameterized code from the UI#27
rogercampos merged 1 commit into
mainfrom
feature/scripts

Conversation

@rogercampos

@rogercampos rogercampos commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What

A second vertical alongside backfills: Scripts — arbitrary one-shot parameterized code, written in the host app, triggered from the DataDrip UI.

# app/scripts/backfill_company_timezones.rb
class BackfillCompanyTimezones < DataDrip::Script
  description "Recomputes the timezone of a company from its address."

  input :company_id, :integer, required: true
  input :mode, :enum, values: %w[fast thorough], required: true
  input :dry_run, :boolean, default: true

  def call
    log "Recomputing timezone for company #{company_id}..."
    # ...
  end
end
Screenshot 2026-07-23 at 09 27 57 Screenshot 2026-07-23 at 09 28 14

How it works

  • Typed input DSL: input name, type, default:, required: — the form at /data_drip/script_runs/new is auto-generated from the declarations, values are coerced server-side via ActiveModel types, and required inputs are validated before the run is created (required booleans accept false but not missing — inclusion validation, not presence).
  • Permanent history: every run is recorded in the new data_drip_script_runs table — who ran it, inputs, full log output, error message + backtrace, status, and created/start/started/finished timestamps. Runs are only deletable while still enqueued.
  • Execution: a single ScriptRunner job (configurable DataDrip.script_queue_name) runs #call once. Scripts emit output through a log API that persists timestamped lines immediately (update_column appends — no $stdout redirection, which is unsafe in multithreaded workers) and streams them live to the show page via a polling Stimulus controller.
  • Hooks: on_script_run_* lifecycle callbacks with the same class-first / global-handler precedence as backfills.

Shared machinery (Backfill behavior unchanged)

  • DataDrip::SchematizedOptions — extracted typed-options DSL now backing both Backfill.attribute and Script.input, adding required: support.
  • DataDrip::BackfillerContext — shared controller concern for backfiller/timezone resolution.
  • The option-form helper is parametrized by field prefix; the existing helper spec (asserting exact backfill field names) guards that backfill form output is byte-identical.
  • Shared sidebar partial with Backfills / Scripts navigation.

One deliberate improvement over the backfill form: on validation errors the script form re-renders input fields server-side with submitted values kept sticky.

Generators / upgrade path

  • rails g data_drip:script MyScript (supports --sorbet)
  • Fresh installs: data_drip:install now also creates app/scripts + the migration
  • Existing installs: rails g data_drip:install_scripts

Testing

  • Full suite: 162 examples, 0 failures; RuboCop clean.
  • ~60 new examples covering the DSL (types, coercion, defaults, required incl. the boolean gotcha), model validations/enqueueing/hooks, job success/failure/output capture, controller (tabs, sticky re-render, updates JSON, destroy rules, AJAX inputs endpoint) and helpers.
  • Smoke-tested in the dummy app (bin/dev): script picker, generated form, live output streaming and failure display.

Notes

  • Datetime inputs get no user-timezone conversion (parity with backfill options); only the top-level "start at" is converted.
  • If the host base_job_class retries, a failed script re-runs from scratch — README documents scripts should be idempotent or hosts should discard_on.
  • This branch includes main's local dev-tooling commit (bin/dev + bootable dummy app); that commit will drop out of the diff once main is pushed.

🤖 Generated with Claude Code

A second vertical alongside backfills: developers write a class in
app/scripts subclassing DataDrip::Script, declaring typed inputs with
the `input` DSL (type, default, required:) and implementing a single
`call` method. The UI at /data_drip/script_runs lists available
scripts, auto-generates a typed input form from the DSL (with
server-side coercion and validation; required booleans accept false
but not missing), and runs the script in a background job.

Every run is recorded permanently in data_drip_script_runs: who ran
it, inputs, log output, error message + backtrace, status and
created/start/started/finished timestamps. Scripts emit output via a
`log` API that persists timestamped lines immediately and streams
them to the show page through a polling Stimulus controller.

Shared machinery is extracted without changing Backfill's public API:

- DataDrip::SchematizedOptions owns the typed-options DSL for both
  Backfill.attribute and Script.input, including required: support,
  :enum server-side validation, and the anonymous model name.
- DataDrip::BackfillerContext shares controller backfiller/timezone
  resolution.
- The option-form helper (typed_option_inputs) is parametrized by
  field prefix; backfill form output is unchanged.

The Scripts UI matches the redesigned admin: it plugs into the shared
header (Backfills/Scripts navigation, dark-mode aware), triggers new
runs through the fuzzy, component-aware combobox class picker reused
from backfills, and renders typed inputs via an AJAX options endpoint.

Also: on_script_run_* lifecycle hooks with class-first/global-handler
precedence, DataDrip.script_queue_name config, `rails g data_drip:script`
and a `rails g data_drip:install_scripts` upgrade generator for existing
installs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rogercampos
rogercampos marked this pull request as ready for review July 23, 2026 07:22
@rogercampos
rogercampos merged commit f5e0253 into main Jul 23, 2026
17 checks passed
@rogercampos
rogercampos deleted the feature/scripts branch July 23, 2026 08:28
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.

1 participant