slackline/stream

Stream a reply into Slack while an agent writes it. You say what the reply should show right now, and this module works out which calls to make.

import slackline/stream
import slackline/stream/httpc as reply

let assert Ok(r) =
  stream.new(client, channel: "C123")
  |> stream.in_thread("1700000000.000100")
  |> stream.to(user: "U123", team: "T123")
  |> reply.open

reply.text(r, "Looking at the failing test")
reply.task(r, stream.Task("t1", "Run the tests", stream.Running, "", ""))
reply.text(r, "Looking at the failing test. It races the clock.")
reply.task(r, stream.Task("t1", "Run the tests", stream.Done, "", "24 passed"))
let assert Ok(finished) = reply.finish(r, closing: "Changed 1 file")

Pass text the whole reply so far, not just the new part. Slack keeps a streamed message open for ten minutes and lets it hold 12,000 characters and 50 tasks. When a message fills up or runs out of time, the reply continues in a new message and the session stays marked as working. If you rewrite text after it has streamed, the new version is posted when the reply finishes. If Slack won’t stream, the reply falls back to a plan card and plain messages.

This module holds the code both targets share. That is the builder, the rules, and the API calls the rules ask for. The rules are pure. Use stream/httpc to run a reply on Erlang and stream/fetch to run one on JavaScript.

Types

What to do after a call.

pub type Answer {
  Continue(Rules)
  RetryIn(milliseconds: Int, rules: Rules)
}

Constructors

  • Continue(Rules)
  • RetryIn(milliseconds: Int, rules: Rules)

    Slack rate limited the call. Try again after this many milliseconds.

Says where a reply goes and how often it updates. Open it with stream/httpc on Erlang or stream/fetch on JavaScript.

pub opaque type Builder

The result of a finished reply. messages lists the ts of every message it posted, oldest first. streamed is False if the reply fell back to plain messages, and problem says why.

pub type Finished {
  Finished(
    messages: List(String),
    streamed: Bool,
    problem: option.Option(String),
  )
}

Constructors

  • Finished(
      messages: List(String),
      streamed: Bool,
      problem: option.Option(String),
    )

How much a streamed message can hold and how long it stays open, in milliseconds. slack_limits stays a little under Slack’s own limits of 12,000 characters, 50 tasks and ten minutes.

pub type Limits {
  Limits(text: Int, tasks: Int, life_ms: Int)
}

Constructors

  • Limits(text: Int, tasks: Int, life_ms: Int)
pub type Next {
  Wait
  Do(
    step: Step,
    answer: fn(Result(String, slackline.Error)) -> Answer,
  )
  Over
}

Constructors

  • Wait

    There is nothing to send yet.

  • Do(
      step: Step,
      answer: fn(Result(String, slackline.Error)) -> Answer,
    )

    Make step, then pass its outcome to answer.

  • Over

    The reply is done.

The result of a call. On success it holds the ts of the message the call posted, or “” if it posted none.

pub type Outcome =
  Result(String, slackline.Error)

Tracks what Slack has been shown of a reply. The rules are pure. Call next_step to get the next call, make it, then pass the outcome to its answer.

pub opaque type Rules

The full state of a reply. It holds the text so far, the tasks, and the title of the plan.

pub type Shows {
  Shows(text: String, tasks: List(Task), title: String)
}

Constructors

  • Shows(text: String, tasks: List(Task), title: String)
pub type Status {
  Pending
  Running
  Done
  Failed
}

Constructors

  • Pending
  • Running
  • Done
  • Failed

A call to Slack that the rules want made.

pub type Step {
  Start(chunks: List(chat.StreamChunk))
  Append(ts: String, chunks: List(chat.StreamChunk))
  Stop(
    ts: String,
    chunks: List(chat.StreamChunk),
    carry_on: Bool,
  )
  Post(text: String)
  PostPlan(title: String, tasks: List(Task))
  UpdatePlan(ts: String, title: String, tasks: List(Task))
}

Constructors

  • Start(chunks: List(chat.StreamChunk))
  • Append(ts: String, chunks: List(chat.StreamChunk))
  • Stop(ts: String, chunks: List(chat.StreamChunk), carry_on: Bool)

    carry_on is True when the reply will continue in a new message. Slack then keeps showing the session as working.

  • Post(text: String)
  • PostPlan(title: String, tasks: List(Task))
  • UpdatePlan(ts: String, title: String, tasks: List(Task))

A step of the work, shown in the reply’s plan. A task with the same id replaces an earlier one. Set details or output to “” to leave it out.

pub type Task {
  Task(
    id: String,
    title: String,
    status: Status,
    details: String,
    output: String,
  )
}

Constructors

  • Task(
      id: String,
      title: String,
      status: Status,
      details: String,
      output: String,
    )

Values

pub const calls_per_flush: Int

How many calls a driver makes in one go before it pauses and lets updates collect again.

pub fn closing(rules: Rules, closing: String) -> Rules

Marks the reply as finishing. closing is a last line to add at the end, or “” for none.

pub fn every(builder: Builder, milliseconds: Int) -> Builder

Sends updates at most once every milliseconds. The default is 1000.

pub fn finished(rules: Rules) -> Finished
pub fn in_thread(builder: Builder, thread_ts: String) -> Builder
pub fn limits(builder: Builder, limits: Limits) -> Builder
pub fn new(
  client: slackline.Client,
  channel channel: String,
) -> Builder

Starts a reply in channel. For a reply in a channel thread, also call in_thread and to. For a reply at the top level of a code channel or a DM, call only to.

pub fn new_rules(limits: Limits) -> Rules
pub fn next_step(rules: Rules, shows: Shows, now_ms: Int) -> Next

Returns the next call to make, given what the reply shows and the current time in milliseconds.

pub const nothing: Shows
pub const slack_limits: Limits
pub fn to(
  builder: Builder,
  user user: String,
  team team: String,
) -> Builder

Sets the user and team the reply is for. Slack needs this to stream in a channel. If either is “”, the reply has no recipient.

pub fn with_task(shows: Shows, task: Task) -> Shows

Adds task to shows. If a task with the same id is already there, task replaces it.

✨ Search Document