Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ and this project adheres to

### Fixed

- Fix canvas "lockup" after AI chat errors, prevent sending empty message to AI
[3605](https://github.com/OpenFn/lightning/issues/3605)
- Fix memory bloat on dataclip viewer in dataclip detail page
[#3641](https://github.com/OpenFn/lightning/issues/3641)
- Ameliorate memory usage when scrubbing dataclips for security
Expand Down
37 changes: 33 additions & 4 deletions lib/lightning_web/live/ai_assistant/component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ defmodule LightningWeb.AiAssistant.Component do
socket
) do
cleared_params = Map.put(params, "content", nil)
trimmed_content = if is_binary(content), do: String.trim(content), else: ""

cond do
not socket.assigns.can_edit ->
Expand All @@ -263,6 +264,23 @@ defmodule LightningWeb.AiAssistant.Component do
socket.assigns.ai_limit_result != :ok ->
{:noreply, socket}

trimmed_content == "" ->
changeset = socket.assigns.handler.validate_form(%{"content" => ""})

changeset =
Ecto.Changeset.add_error(
changeset,
:content,
"Please enter a message before sending"
)

{:noreply,
socket
|> assign(
changeset: changeset,
alert: "Please enter a message before sending"
)}

true ->
{:noreply,
socket
Expand All @@ -272,7 +290,7 @@ defmodule LightningWeb.AiAssistant.Component do
:changeset,
socket.assigns.handler.validate_form(cleared_params)
)
|> save_message(socket.assigns.action, content)}
|> save_message(socket.assigns.action, trimmed_content)}
end
end

Expand Down Expand Up @@ -447,7 +465,9 @@ defmodule LightningWeb.AiAssistant.Component do
end

defp handle_save_error(socket, error) do
assign(socket, alert: socket.assigns.handler.error_message(error))
socket
|> assign(alert: socket.assigns.handler.error_message(error))
|> assign(pending_message: AsyncResult.ok(nil))
end

defp redirect_url(base_url, query_params) do
Expand Down Expand Up @@ -679,11 +699,11 @@ defmodule LightningWeb.AiAssistant.Component do
<.simple_button_with_tooltip
id={"ai-assistant-form-submit-btn-#{@id}"}
type="submit"
disabled={@disabled}
disabled={@disabled || form_content_empty?(@form[:content].value)}
form={@form_id}
class={[
"p-1.5 rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 transition-all duration-200 flex items-center justify-center h-7 w-7",
if(@disabled,
if(@disabled || form_content_empty?(@form[:content].value),
do:
"text-gray-400 bg-gray-300 cursor-not-allowed focus:ring-gray-300",
else:
Expand Down Expand Up @@ -933,6 +953,15 @@ defmodule LightningWeb.AiAssistant.Component do
Timex.diff(DateTime.utc_now(), datetime, :hours) < 1
end

defp form_content_empty?(value) do
case value do
nil -> true
"" -> true
content when is_binary(content) -> String.trim(content) == ""
_ -> false
end
end

defp render_onboarding(assigns) do
assigns = assign(assigns, ai_quote: Quotes.random_enabled())

Expand Down
11 changes: 10 additions & 1 deletion lib/lightning_web/live/ai_assistant/mode_behavior.ex
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,20 @@ defmodule LightningWeb.Live.AiAssistant.ModeBehavior do
field :content, :string
end

def changeset(params) do
@doc false
def changeset(params \\ %{}) do
%__MODULE__{}
|> cast(params, [:content])
|> validate_required([:content],
message: "Please enter a message before sending"
)
|> validate_length(:content,
min: 1,
message: "Please enter a message before sending"
)
end

@doc false
def extract_options(_changeset), do: []
end

Expand Down
7 changes: 7 additions & 0 deletions lib/lightning_web/live/ai_assistant/modes/job_code.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ defmodule LightningWeb.Live.AiAssistant.Modes.JobCode do
def changeset(params) do
%__MODULE__{}
|> cast(params, [:content])
|> validate_required([:content],
message: "Please enter a message before sending"
)
|> validate_length(:content,
min: 1,
message: "Please enter a message before sending"
)
|> cast_embed(:options, with: &options_changeset/2)
end

Expand Down
6 changes: 4 additions & 2 deletions test/lightning_web/live/ai_assistant_live_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,8 @@ defmodule LightningWeb.AiAssistantLiveTest do
assert has_element?(input_element)
refute render(input_element) =~ "disabled=\"disabled\""
assert has_element?(submit_btn)
refute render(submit_btn) =~ "disabled=\"disabled\""
# Submit button should be disabled when no content is entered
assert render(submit_btn) =~ "disabled=\"disabled\""

html =
view
Expand Down Expand Up @@ -2354,7 +2355,8 @@ defmodule LightningWeb.AiAssistantLiveTest do
)

refute render(input_element) =~ "disabled=\"disabled\""
refute render(submit_btn) =~ "disabled=\"disabled\""
# Submit button should be disabled when no content is entered
assert render(submit_btn) =~ "disabled=\"disabled\""

refute render(input_element) =~ "Save your workflow first"
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,4 +289,45 @@ defmodule LightningWeb.WorkflowLive.AiAssistant.ComponentTest do
]
end
end

describe "form validation" do
alias LightningWeb.Live.AiAssistant.Modes.WorkflowTemplate

test "JobCode Form validates empty content" do
changeset = JobCode.Form.changeset(%{"content" => ""})

assert changeset.valid? == false
assert Keyword.has_key?(changeset.errors, :content)
{msg, _opts} = changeset.errors[:content]
assert msg == "Please enter a message before sending"
end

test "JobCode validate_form includes content validation" do
changeset = JobCode.validate_form(%{"content" => nil})

assert changeset.valid? == false
assert Keyword.has_key?(changeset.errors, :content)
end

test "WorkflowTemplate DefaultForm validates empty content" do
changeset = WorkflowTemplate.DefaultForm.changeset(%{"content" => ""})

assert changeset.valid? == false
assert Keyword.has_key?(changeset.errors, :content)
{msg, _opts} = changeset.errors[:content]
assert msg == "Please enter a message before sending"
end

test "form validation accepts valid content" do
# JobCode
changeset = JobCode.validate_form(%{"content" => "Help me with my code"})
assert changeset.valid? == true

# WorkflowTemplate
changeset =
WorkflowTemplate.validate_form(%{"content" => "Create a workflow"})

assert changeset.valid? == true
end
end
end