Skip to content

Commit

Permalink
allow for callbacks to fail
Browse files Browse the repository at this point in the history
  • Loading branch information
ProducerMatt committed May 29, 2024
1 parent c80a045 commit fbaffd7
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 37 deletions.
141 changes: 104 additions & 37 deletions lib/plugin.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ defmodule Plugin do
alias Stampede, as: S
alias S.{MsgReceived, ResponseToPost, InteractionForm}
require InteractionForm
require Aja
@first_response_timeout 500

@doc """
Expand Down Expand Up @@ -92,6 +93,29 @@ defmodule Plugin do
MapSet.subset?(enabled, Plugin.ls())
end

def try_callback(response = %ResponseToPost{}, traceback) do
{m, f, a} = response.callback

followup_r =
apply(m, f, a)

new_tb =
Stampede.Traceback.append(
traceback,
if followup_r do
{:callback_called, followup_r.text, followup_r.why}
else
:callback_called_and_declined
end
)

if followup_r do
{followup_r, new_tb}
else
{nil, new_tb}
end
end

@type! job_result ::
{:job_error, :timeout}
| {:job_error, tuple()}
Expand Down Expand Up @@ -261,28 +285,8 @@ defmodule Plugin do

# we can't get a response until we run this callback
# TODO: let callback decide to not respond, and fall back to the next highest priority response
%ResponseToPost{callback: {mod, fun, args}} ->
followup =
apply(mod, fun, args)

new_tb =
Stampede.Traceback.append(
traceback,
{:callback_called, followup.text, followup.why}
)

{:ok, iid} =
S.InteractionForm.new(
service: cfg.service,
plugin: chosen_response.origin_plug,
msg: msg,
response: followup,
channel_lock: followup.channel_lock,
traceback: new_tb
)
|> S.Interact.prepare_interaction!()

{followup, iid}
%ResponseToPost{callback: cb} ->

Check warning on line 288 in lib/plugin.ex

View workflow job for this annotation

GitHub Actions / Build and test

variable "cb" is unused (if the variable is not meant to be used, prefix it with an underscore)
raise "Callback should have been done by now!"
end
end

Expand Down Expand Up @@ -373,21 +377,70 @@ defmodule Plugin do
|> do_rr(nil, Aja.Vector.new())
end

def do_rr([], chosen_response, traceback) do
%{
r: chosen_response,
tb: traceback
}
def do_rr([], possible_responses, traceback) do
cond do
possible_responses == nil ->
%{r: nil, tb: traceback}

possible_responses && Aja.vec_size(possible_responses) == 1 ->
%{r: Aja.Vector.at!(possible_responses, 0), tb: traceback}

true ->
# # DEBUG
# Aja.Vector.foldl(possible_responses, 0, fn
# r = %ResponseToPost{}, acc ->
# unless r.callback do
# acc == 0 || raise "This should be the first callback"
# acc + 1
# else
# acc == 0 || raise "There shouldn't be callbacks after a text post"
# acc
# end
# other, _ ->
# raise "Expected response, got #{other |> inspect(pretty: true)}"
# end)

{r, tb} =
Aja.Vector.foldl(possible_responses, {nil, traceback}, fn
current_r = %ResponseToPost{}, {nil, tb} ->
# response not yet chosen
if current_r.callback do
try_callback(current_r, tb)
else
{current_r, tb}
end

current_r = %ResponseToPost{}, {chosen_r = %ResponseToPost{}, tb} ->
# response already chosen
if chosen_r.confidence < current_r.confidence do
if current_r.callback do
try_callback(current_r, tb)
else
{current_r, tb}
end
else
{chosen_r, tb}
end

other, _ ->
raise "Should never happen. #{other |> inspect(pretty: true)}"
end)

%{
r: r,
tb: tb
}
end
end

def do_rr(
[{plug, {:job_ok, nil}} | rest],
chosen_response,
possible_responses,
traceback
) do
do_rr(
rest,
chosen_response,
possible_responses,
S.Traceback.append(
traceback,
{:declined_to_answer, plug}
Expand All @@ -397,12 +450,12 @@ defmodule Plugin do

def do_rr(
[{plug, {:job_error, :timeout}} | rest],
chosen_response,
possible_responses,
traceback
) do
do_rr(
rest,
chosen_response,
possible_responses,
S.Traceback.append(
traceback,
{:timeout, plug}
Expand All @@ -411,8 +464,8 @@ defmodule Plugin do
end

def do_rr(
[{plug, {:job_ok, response}} | rest],
chosen_response,
[{plug, {:job_ok, response = %ResponseToPost{}}} | rest],
possible_responses,
traceback
) do
responded_log =
Expand All @@ -422,20 +475,34 @@ defmodule Plugin do
{:replied_with_text, plug, response.confidence, response.text, response.why}
end

# assuming first response is chosen response, meaning pre-sorted
if chosen_response == nil do
# assuming first response is highest priority response, meaning pre-sorted
if possible_responses == nil do
do_rr(
rest,
response,
Aja.vec([response]),
S.Traceback.append(
traceback,
{:response_was_chosen, responded_log}
)
)
else
# if response is a callback, we need an alternative should the callback fail.
# Keep saving responses until one isn't a callback, so we aren't left empty handed.
non_callback_available =
Aja.Vector.at!(possible_responses, -1)
|> Map.fetch!(:callback)
|> Kernel.===(nil)

updated_responses =
if non_callback_available do
possible_responses
else
Aja.Vector.append(possible_responses, response)
end

do_rr(
rest,
chosen_response,
updated_responses,
S.Traceback.append(
traceback,
responded_log
Expand Down
19 changes: 19 additions & 0 deletions lib/plugins/test.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ defmodule Plugins.Test do
[
{"ping", "pong!"},
{"callback", "(shows callback replies work)"},
{"callback fail", "(shows callbacks can give up)"},
{"a", "(shows channel locks work)"},
{"timeout", "(shows that plugins which time out won't disrupt other plugins)"},
{"raise", "(raises an error which should be reported)"},
Expand Down Expand Up @@ -51,6 +52,15 @@ defmodule Plugins.Test do
callback: {__MODULE__, :callback_example, [num, msg.id]}
)

"callback fail" ->
S.ResponseToPost.new(
confidence: 10,
text: nil,
origin_msg_id: msg.id,
why: ["They want to test callback fails."],
callback: {__MODULE__, :callback_example, [:fail, msg.id]}
)

# test channel locks
"a" ->
S.ResponseToPost.new(
Expand All @@ -77,6 +87,15 @@ defmodule Plugins.Test do
end
end

def callback_example(:fail, msg_id) do
S.ResponseToPost.new(
confidence: 0,
origin_msg_id: msg_id,
text: "THIS SHOULDNT BE SHOWN",
why: "Testing callbacks that fail"
)
end

def callback_example(num, msg_id) when is_number(num) do
S.ResponseToPost.new(
confidence: 10,
Expand Down
6 changes: 6 additions & 0 deletions lib/stampede/traceback.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ defmodule Stampede.Traceback do
]
end

def do_single_transform(:callback_called_and_declined) do
[
"\nTop response was a callback, so i called it. But it decided it had nothing to say."
]
end

def do_single_transform({:channel_lock_triggered, channel_id, m, f, text, why}) do
[
"Channel ",
Expand Down
5 changes: 5 additions & 0 deletions test/stampede_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ defmodule StampedeTest do
assert String.starts_with?(r.text, "Called back with")
end

test "plugin with failing callback", s do
r = D.send_msg(s.id, :t1, :u1, "!callback fail")
assert String.starts_with?(r.text, @confused_response)
end

test "plugin timeout", s do
r = D.send_msg(s.id, :t1, :u1, "!timeout")
assert r.text == @confused_response
Expand Down

0 comments on commit fbaffd7

Please sign in to comment.