From f6ffb17329404336457646a8a17bb680233f4cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Niemier?= Date: Thu, 26 Oct 2023 00:13:41 +0200 Subject: [PATCH] fix: reduce use of `length/1` whenever not needed --- lib/logflare/account_email.ex | 2 +- lib/logflare/auth.ex | 2 +- lib/logflare/endpoints/cache.ex | 2 +- lib/logflare/logs/log_events.ex | 10 +-- lib/logflare/logs/logs.ex | 3 +- lib/logflare/logs/lql/lql_encoder.ex | 3 +- lib/logflare/logs/lql/lql_parser.ex | 10 +-- .../logs/search/logs_search_operations.ex | 2 +- .../search/logs_search_operations_helpers.ex | 2 +- lib/logflare/logs/search/lql_validator.ex | 6 +- .../logs/source_parsers/syslog_parser.ex | 2 +- .../eq_deep_field_types_validator.ex | 2 +- lib/logflare/single_tenant.ex | 10 +-- lib/logflare/source/rate_counter_server.ex | 24 +++---- lib/logflare/sources.ex | 11 +--- lib/logflare/sources/rules.ex | 6 +- lib/logflare/sources/source_routing.ex | 4 +- lib/logflare/utils/list.ex | 54 +++++++++++++++ .../live/endpoints/actions/show.html.heex | 20 +++--- .../live/search_live/logs_search_lv.ex | 2 +- lib/logflare_web/live/source_backends_live.ex | 5 +- .../templates/rule/source_rules.html.leex | 2 +- .../templates/source/dashboard.html.eex | 6 +- test/logflare/utils/list_test.exs | 66 +++++++++++++++++++ .../live/search_live/logs_search_lv_test.exs | 3 +- 25 files changed, 193 insertions(+), 66 deletions(-) create mode 100644 lib/logflare/utils/list.ex create mode 100644 test/logflare/utils/list_test.exs diff --git a/lib/logflare/account_email.ex b/lib/logflare/account_email.ex index ea5919190..2fc862fe2 100644 --- a/lib/logflare/account_email.ex +++ b/lib/logflare/account_email.ex @@ -150,7 +150,7 @@ defmodule Logflare.AccountEmail do diff = diff_schema(schema_to_list(formatted_new), schema_to_list(formatted_old)) - if diff == [] do + if Enum.empty?(diff) do # Something generates BOOL and something else generates BOOLEAN which causes this Logger.error("Schema update email send with no new fields.", source_id: source.token, diff --git a/lib/logflare/auth.ex b/lib/logflare/auth.ex index 25d504cf2..9494095d3 100644 --- a/lib/logflare/auth.ex +++ b/lib/logflare/auth.ex @@ -143,7 +143,7 @@ defmodule Logflare.Auth do defp check_scopes(token_scopes, required) do cond do "private" in token_scopes -> :ok - required == [] -> :ok + Enum.empty?(required) -> :ok Enum.any?(token_scopes, fn scope -> scope in required end) -> :ok true -> {:error, :unauthorized} end diff --git a/lib/logflare/endpoints/cache.ex b/lib/logflare/endpoints/cache.ex index fcbbfd5bb..fb249ca00 100644 --- a/lib/logflare/endpoints/cache.ex +++ b/lib/logflare/endpoints/cache.ex @@ -173,7 +173,7 @@ defmodule Logflare.Endpoints.Cache do refresh(proactive_querying_ms(state)) {:noreply, %{state | query_tasks: tasks}} - tasks == [] -> + Enum.empty?(tasks) -> {:stop, :normal, state} true -> diff --git a/lib/logflare/logs/log_events.ex b/lib/logflare/logs/log_events.ex index 4fc519dc9..47a2d3c10 100644 --- a/lib/logflare/logs/log_events.ex +++ b/lib/logflare/logs/log_events.ex @@ -34,7 +34,7 @@ defmodule Logflare.Logs.LogEvents do le = LogEvent.make_from_db(row, %{source: source}) {:ok, le} - %{rows: rows} when length(rows) >= 1 -> + %{rows: rows} -> row = Enum.find(rows, &(&1.id == id)) le = LogEvent.make_from_db(row, %{source: source}) {:ok, le} @@ -150,14 +150,14 @@ defmodule Logflare.Logs.LogEvents do defp process(result) do case result do - {:ok, %{rows: rows}} when length(rows) > 1 -> - {:error, "Multiple rows returned, expected one"} + {:ok, %{rows: []}} -> + nil {:ok, %{rows: [row]}} -> row - {:ok, %{rows: []}} -> - nil + {:ok, %{rows: _rows}} -> + {:error, "Multiple rows returned, expected one"} {:error, error} -> {:error, error} diff --git a/lib/logflare/logs/logs.ex b/lib/logflare/logs/logs.ex index 830772089..281da5ee2 100644 --- a/lib/logflare/logs/logs.ex +++ b/lib/logflare/logs/logs.ex @@ -68,7 +68,8 @@ defmodule Logflare.Logs do ) when is_binary(drop_lql_string) do cond do - length(filters) >= 1 && SourceRouting.route_with_lql_rules?(le, %Rule{lql_filters: filters}) -> + not Enum.empty?(filters) and + SourceRouting.route_with_lql_rules?(le, %Rule{lql_filters: filters}) -> Map.put(le, :drop, true) true -> diff --git a/lib/logflare/logs/lql/lql_encoder.ex b/lib/logflare/logs/lql/lql_encoder.ex index 7fede34a5..7ceeaed83 100644 --- a/lib/logflare/logs/lql/lql_encoder.ex +++ b/lib/logflare/logs/lql/lql_encoder.ex @@ -23,7 +23,8 @@ defmodule Logflare.Lql.Encoder do |> Enum.map_join(" ", &to_fragment/1) |> String.replace("chart:timestamp", "") - {"m.level", filter_rules} when length(filter_rules) >= 2 -> + # `filter_rules` has at least 2 entries + {"m.level", [_, _ | _] = filter_rules} -> {min_level, max_level} = Enum.min_max_by(filter_rules, &Parser.Helpers.get_level_order(&1.value)) diff --git a/lib/logflare/logs/lql/lql_parser.ex b/lib/logflare/logs/lql/lql_parser.ex index 228121ee0..fc494284c 100644 --- a/lib/logflare/logs/lql/lql_parser.ex +++ b/lib/logflare/logs/lql/lql_parser.ex @@ -123,14 +123,16 @@ defmodule Logflare.Lql.Parser do defp maybe_cast_value(c, {:list, type}), do: maybe_cast_value(c, type) - defp maybe_cast_value(%{values: values, value: nil} = c, type) when length(values) >= 1 do + defp maybe_cast_value(%{values: values, value: nil} = c, type) when values != [] do %{ c | values: values - |> Enum.map(&%{value: &1, path: c.path}) - |> Enum.map(&maybe_cast_value(&1, type)) - |> Enum.map(& &1.value) + |> Enum.map(fn data -> + %{value: data, path: c.path} + |> maybe_cast_value(type) + |> Map.fetch!(:value) + end) } end diff --git a/lib/logflare/logs/search/logs_search_operations.ex b/lib/logflare/logs/search/logs_search_operations.ex index f68b7556a..e8fcf2735 100644 --- a/lib/logflare/logs/search/logs_search_operations.ex +++ b/lib/logflare/logs/search/logs_search_operations.ex @@ -85,7 +85,7 @@ defmodule Logflare.Logs.SearchOperations do so.tailing? and not Enum.empty?(so.lql_ts_filters) -> Utils.halt(so, @timestamp_filter_with_tailing) - length(so.chart_rules) > 1 -> + Logflare.Utils.List.at_least?(so.chart_rules, 2) -> Utils.halt(so, "Only one chart rule can be used in a query") match?([_], so.chart_rules) and diff --git a/lib/logflare/logs/search/logs_search_operations_helpers.ex b/lib/logflare/logs/search/logs_search_operations_helpers.ex index f892068a2..d84755bd2 100644 --- a/lib/logflare/logs/search/logs_search_operations_helpers.ex +++ b/lib/logflare/logs/search/logs_search_operations_helpers.ex @@ -30,7 +30,7 @@ defmodule Logflare.Logs.SearchOperations.Helpers do %{min: min, max: max, message: message} end - def get_min_max_filter_timestamps(ts_filters, _chart_period) when length(ts_filters) > 1 do + def get_min_max_filter_timestamps(ts_filters, _chart_period) when is_list(ts_filters) do {min, max} = ts_filters |> Enum.map(& &1.value) diff --git a/lib/logflare/logs/search/lql_validator.ex b/lib/logflare/logs/search/lql_validator.ex index 321606e45..89ca3f785 100644 --- a/lib/logflare/logs/search/lql_validator.ex +++ b/lib/logflare/logs/search/lql_validator.ex @@ -2,6 +2,8 @@ defmodule Logflare.Lql.Validator do @moduledoc false import Logflare.Logs.SearchOperations.Helpers + alias Logflare.Utils.List, as: ListH + @timestamp_filter_with_tailing "Timestamp filters can't be used if live tail search is active" @default_max_n_chart_ticks 250 @@ -20,10 +22,10 @@ defmodule Logflare.Lql.Validator do tailing? and not Enum.empty?(lql_ts_filters) -> @timestamp_filter_with_tailing - length(chart_rules) > 1 -> + ListH.at_least?(chart_rules, 2) -> "Only one chart rule can be used in a LQL query" - match?([_], chart_rules) and + ListH.exactly?(chart_rules, 1) and hd(chart_rules).value_type not in ~w[integer float]a and hd(chart_rules).path != "timestamp" -> chart_rule = hd(chart_rules) diff --git a/lib/logflare/logs/source_parsers/syslog_parser.ex b/lib/logflare/logs/source_parsers/syslog_parser.ex index dab0b3f06..501847d98 100644 --- a/lib/logflare/logs/source_parsers/syslog_parser.ex +++ b/lib/logflare/logs/source_parsers/syslog_parser.ex @@ -128,7 +128,7 @@ defmodule Logflare.Logs.SyslogParser do defp merge_syslog_sd(tokens) when is_list(tokens) do {sd_element_values, new_tokens} = Keyword.pop_values(tokens, :sd_element) - if length(sd_element_values) > 0 do + if not Enum.empty?(sd_element_values) do sd = sd_element_values |> Enum.map(fn sd_element -> diff --git a/lib/logflare/logs/validators/eq_deep_field_types_validator.ex b/lib/logflare/logs/validators/eq_deep_field_types_validator.ex index d7ffe070f..0ae3ccd51 100644 --- a/lib/logflare/logs/validators/eq_deep_field_types_validator.ex +++ b/lib/logflare/logs/validators/eq_deep_field_types_validator.ex @@ -107,7 +107,7 @@ defmodule Logflare.Logs.Validators.EqDeepFieldTypes do is_list_of_enums(merged) -> deep_merge_enums(merged) - merged == [] -> + Enum.empty?(merged) -> {:list, :empty} is_homogenous_list(merged) -> diff --git a/lib/logflare/single_tenant.ex b/lib/logflare/single_tenant.ex index ba96ebbb6..79eac3624 100644 --- a/lib/logflare/single_tenant.ex +++ b/lib/logflare/single_tenant.ex @@ -261,13 +261,13 @@ defmodule Logflare.SingleTenant do seed_plan = if default_plan, do: :ok seed_sources = - if default_user do - if Sources.list_sources_by_user(default_user) |> length() > 0, do: :ok + if default_user && not Enum.empty?(Sources.list_sources_by_user(default_user)) do + :ok end seed_endpoints = - if default_user do - if Endpoints.list_endpoints_by(user_id: default_user.id) |> length() > 0, do: :ok + if default_user && not Enum.empty?(Endpoints.list_endpoints_by(user_id: default_user.id)) do + :ok end source_schemas_updated = @@ -310,7 +310,7 @@ defmodule Logflare.SingleTenant do state.field_count > 3 end - Enum.all?(checks) and length(sources) > 0 + Enum.all?(checks) and not Enum.empty?(sources) else false end diff --git a/lib/logflare/source/rate_counter_server.ex b/lib/logflare/source/rate_counter_server.ex index 16242db1f..3ccb5b80f 100644 --- a/lib/logflare/source/rate_counter_server.ex +++ b/lib/logflare/source/rate_counter_server.ex @@ -140,18 +140,12 @@ defmodule Logflare.Source.RateCounterServer do # TODO: optimize by not recalculating total sum and average new_queue = LQueue.push(bucket.queue, state.last_rate) - average = + stats = new_queue |> Enum.to_list() - |> average() - |> round() + |> stats() - sum = - new_queue - |> Enum.to_list() - |> Enum.sum() - - {length, %{bucket | queue: new_queue, average: average, sum: sum}} + {length, %{bucket | queue: new_queue, average: stats.avg, sum: stats.sum}} end end) end @@ -274,8 +268,16 @@ defmodule Logflare.Source.RateCounterServer do Sources.Counters.get_inserts(source_id) end - def average(xs) when is_list(xs) do - Enum.sum(xs) / length(xs) + def stats(xs) when is_list(xs) do + {total, count} = + Enum.reduce(xs, {0, 0}, fn v, {total, count} -> + {total + v, count + 1} + end) + + %{ + avg: total / count, + sum: total + } end defp init_counters(source_id, bigquery_project_id) when is_atom(source_id) do diff --git a/lib/logflare/sources.ex b/lib/logflare/sources.ex index 357417039..dc36c24ae 100644 --- a/lib/logflare/sources.ex +++ b/lib/logflare/sources.ex @@ -263,7 +263,7 @@ defmodule Logflare.Sources do rules = for rule <- rules do case rule do - %Rule{lql_filters: lql_filters} when length(lql_filters) >= 1 -> + %Rule{lql_filters: lql_filters} when lql_filters != [] -> rule %Rule{regex_struct: rs} when not is_nil(rs) -> @@ -341,15 +341,10 @@ defmodule Logflare.Sources do %{source | metrics: new_metrics} end - def valid_source_token_param?(string) when is_binary(string) do - case String.length(string) === 36 && Ecto.UUID.cast(string) do - {:ok, _} -> true - _ -> false - end + def valid_source_token_param?(string) do + match?({:ok, _}, Ecto.UUID.dump(string)) end - def valid_source_token_param?(_), do: false - def delete_slack_hook_url(source) do source |> Source.changeset(%{slack_hook_url: nil}) diff --git a/lib/logflare/sources/rules.ex b/lib/logflare/sources/rules.ex index 49aad14d5..b816ac796 100644 --- a/lib/logflare/sources/rules.ex +++ b/lib/logflare/sources/rules.ex @@ -37,11 +37,11 @@ defmodule Logflare.Rules do %Rule{regex_struct: rs}, _ when is_nil(rs) -> {:cont, false} - %Rule{regex_struct: rs, lql_filters: lf}, _ when is_map(rs) and length(lf) >= 1 -> - {:cont, false} - %Rule{regex_struct: rs, lql_filters: []}, _ when is_map(rs) -> {:halt, true} + + %Rule{regex_struct: rs}, _ when is_map(rs) -> + {:cont, false} end) end diff --git a/lib/logflare/sources/source_routing.ex b/lib/logflare/sources/source_routing.ex index 3c0df6569..d7622f854 100644 --- a/lib/logflare/sources/source_routing.ex +++ b/lib/logflare/sources/source_routing.ex @@ -24,7 +24,7 @@ defmodule Logflare.Logs.SourceRouting do rule.regex_struct || if(rule.regex != nil, do: Regex.compile!(rule.regex), else: nil) cond do - length(rule.lql_filters) >= 1 and route_with_lql_rules?(le, rule) -> + not Enum.empty?(rule.lql_filters) and route_with_lql_rules?(le, rule) -> do_route(le, rule) regex_struct != nil and Regex.match?(regex_struct, body["event_message"]) -> @@ -53,7 +53,7 @@ defmodule Logflare.Logs.SourceRouting do @spec route_with_lql_rules?(LE.t(), Rule.t()) :: boolean() def route_with_lql_rules?(%LE{body: le_body}, %Rule{lql_filters: lql_filters}) - when length(lql_filters) >= 1 do + when lql_filters != [] do lql_rules_match? = Enum.reduce_while(lql_filters, true, fn lql_filter, _acc -> %Lql.FilterRule{path: path, value: value, operator: operator, modifiers: mds} = lql_filter diff --git a/lib/logflare/utils/list.ex b/lib/logflare/utils/list.ex new file mode 100644 index 000000000..883d2aaf1 --- /dev/null +++ b/lib/logflare/utils/list.ex @@ -0,0 +1,54 @@ +defmodule Logflare.Utils.List do + @doc """ + Check if `list`'s lenght is *exactly* `n` + + It does the same check as `length(list) == n`, but it will do at most `n` + steps through the list, while `length/1` will always step through whole `list`. + This can have negative performance impact if `lenght(list)` is much larger + than `n`. + + ## Example + + ```elixir + iex> #{__MODULE__}.exactly?([1, 2, 3], 2) + false + iex> #{__MODULE__}.exactly?([1, 2, 3], 3) + true + iex> #{__MODULE__}.exactly?([1, 2, 3], 4) + false + ``` + """ + @spec exactly?(list :: list(), n :: non_neg_integer()) :: boolean() + def exactly?([], 0), do: true + def exactly?([], _), do: false + def exactly?([_ | _], 0), do: false + + def exactly?([_ | rest], n) when is_integer(n) and n > 0, + do: exactly?(rest, n - 1) + + @doc """ + Check if `list`'s length is *at least* of `n` + + It does the same check as `length(list) >= n`, but it will do at most `n` + steps through the list, while `length/1` will always step through whole `list`. + This can have negative performance impact if `lenght(list)` is much larger + than `n`. + + ## Example + + ```elixir + iex> #{__MODULE__}.at_least?([1, 2, 3], 2) + true + iex> #{__MODULE__}.at_least?([1, 2, 3], 3) + true + iex> #{__MODULE__}.at_least?([1, 2, 3], 4) + false + ``` + """ + @spec at_least?(list :: list(), n :: non_neg_integer()) :: boolean() + def at_least?([], n) when n > 0, do: false + def at_least?(list, 0) when is_list(list), do: true + + def at_least?([_ | rest], n) when is_integer(n) and n > 0, + do: at_least?(rest, n - 1) +end diff --git a/lib/logflare_web/live/endpoints/actions/show.html.heex b/lib/logflare_web/live/endpoints/actions/show.html.heex index 4dbdc617d..43911974f 100644 --- a/lib/logflare_web/live/endpoints/actions/show.html.heex +++ b/lib/logflare_web/live/endpoints/actions/show.html.heex @@ -74,10 +74,12 @@ curl "<%= url(~p"/api/endpoints/query/#{@show_endpoint.token}") %>" \ -H 'X-API-KEY: YOUR-ACCESS-TOKEN' \ -H 'Content-Type: application/json; charset=utf-8' - 0}>\ - 0}> - -G <%= Enum.map(@declared_params, fn p -> "-d \"#{p}=VALUE\"" end) |> Enum.join(" ") %> - + <%= if not Enum.empty?(@declared_params) do %> + \ + + -G <%= Enum.map_join(@declared_params, " ", fn p -> "-d \"#{p}=VALUE\"" end) %> + + <% end %> @@ -85,10 +87,12 @@ curl "<%= url(~p"/api/endpoints/query/#{@show_endpoint.name}") %>" \ -H 'X-API-KEY: YOUR-ACCESS-TOKEN' \ -H 'Content-Type: application/json; charset=utf-8' - 0}>\ - 0}> - -G <%= Enum.map(@declared_params, fn p -> "-d \"#{p}=VALUE\"" end) |> Enum.join(" ") %> - + <%= if not Enum.empty?(@declared_params) do %> + \ + + -G <%= Enum.map_join(@declared_params, " ", fn p -> "-d \"#{p}=VALUE\"" end) %> + + <% end %> diff --git a/lib/logflare_web/live/search_live/logs_search_lv.ex b/lib/logflare_web/live/search_live/logs_search_lv.ex index 15c6e2592..afe79fc9f 100644 --- a/lib/logflare_web/live/search_live/logs_search_lv.ex +++ b/lib/logflare_web/live/search_live/logs_search_lv.ex @@ -724,7 +724,7 @@ defmodule LogflareWeb.Source.SearchLV do defp warning_message(assigns, search_op) do tailing? = assigns.tailing? querystring = assigns.querystring - log_events_empty? = search_op.events.rows == [] + log_events_empty? = Enum.empty?(search_op.events.rows) cond do log_events_empty? and not tailing? -> diff --git a/lib/logflare_web/live/source_backends_live.ex b/lib/logflare_web/live/source_backends_live.ex index 027856c98..f2d8f7445 100644 --- a/lib/logflare_web/live/source_backends_live.ex +++ b/lib/logflare_web/live/source_backends_live.ex @@ -9,8 +9,7 @@ defmodule LogflareWeb.SourceBackendsLive do
<%= if !@show_create_form do %> - <% end %> - <%= if @show_create_form do %> + <% else %> <.form :let={f} for={%{}} @@ -60,7 +59,7 @@ defmodule LogflareWeb.SourceBackendsLive do <%= submit("Add", class: "btn btn-primary") %> <% end %> -
+
Backends:
  • diff --git a/lib/logflare_web/templates/rule/source_rules.html.leex b/lib/logflare_web/templates/rule/source_rules.html.leex index a3a8aada0..3608b62de 100644 --- a/lib/logflare_web/templates/rule/source_rules.html.leex +++ b/lib/logflare_web/templates/rule/source_rules.html.leex @@ -39,7 +39,7 @@
<% end %>
    - <%= if @rules == [] do %> + <%= if Enum.empty?(@rules) do %>
  • No rules yet...
  • diff --git a/lib/logflare_web/templates/source/dashboard.html.eex b/lib/logflare_web/templates/source/dashboard.html.eex index a50c606d5..5086bfff1 100644 --- a/lib/logflare_web/templates/source/dashboard.html.eex +++ b/lib/logflare_web/templates/source/dashboard.html.eex @@ -25,7 +25,7 @@
    Saved Searches
      - <%= if Enum.map(@sources, &Map.get(&1, :saved_searches)) |> Enum.concat == [], do: "Your saved searches will show up here. Save some searches!" %> + <%= if Enum.all?(@sources, &Map.get(&1, :saved_searches) == []), do: "Your saved searches will show up here. Save some searches!" %> <%= for source <- @sources do %> <%= for saved_search <- source.saved_searches do %>
    • @@ -52,7 +52,7 @@ <%= link "Create your own Logflare account.", to: Routes.auth_path(@conn, :create_and_sign_in), method: "POST", class: "" %>
    • <% end %> - <%= if @team_users == [], do: "Other teams you are a member of will be listed here." %> + <%= if Enum.empty?(@team_users), do: "Other teams you are a member of will be listed here." %> <%= for team_user <- @team_users do %> <%= if @team.id == team_user.team_id do %>
    • <%= team_user.team.name %>
    • @@ -71,7 +71,7 @@
        - <%= if @sources == [] do %> + <%= if Enum.empty?(@sources) do %>
      • You don't have any sources!
      • Sources are where your log events go.
      • Create one now!
      • diff --git a/test/logflare/utils/list_test.exs b/test/logflare/utils/list_test.exs new file mode 100644 index 000000000..c9cae32b7 --- /dev/null +++ b/test/logflare/utils/list_test.exs @@ -0,0 +1,66 @@ +defmodule Logflare.Utils.ListTest do + use ExUnit.Case, async: true + use ExUnitProperties + + @subject Logflare.Utils.List + + doctest @subject + + describe "exactly?/2" do + test "empty list has length 0" do + assert @subject.exactly?([], 0) + end + + property "returns true if 2nd argument is equal to length" do + check all(lst <- list_of(term())) do + assert @subject.exactly?(lst, length(lst)) + end + end + + property "return false if 2nd argument is greater than length" do + check all(lst <- list_of(term()), delta <- positive_integer()) do + refute @subject.exactly?(lst, length(lst) + delta) + end + end + + property "return false if 2nd argument is less than length" do + check all( + lst <- list_of(term(), min_length: 2), + len = length(lst), + delta <- integer(1..(len - 1)) + ) do + refute @subject.exactly?(lst, len - delta) + end + end + end + + describe "at_least?/2" do + property "any list has at most 0 elements" do + check all(lst <- list_of(term())) do + assert @subject.at_least?(lst, 0) + end + end + + property "list has at most `length(list)` elements" do + check all(lst <- list_of(term())) do + assert @subject.at_least?(lst, length(lst)) + end + end + + property "return false if 2nd argument is greater than length" do + check all(lst <- list_of(term()), delta <- positive_integer()) do + refute @subject.at_least?(lst, length(lst) + delta) + end + end + + property "list has at most `length(list) - delta` elements" do + check all( + lst <- list_of(term(), min_length: 2), + len = length(lst), + delta <- integer(1..(len - 1)) + ) do + assert @subject.at_least?(lst, len - delta) + end + end + end +end diff --git a/test/logflare_web/live/search_live/logs_search_lv_test.exs b/test/logflare_web/live/search_live/logs_search_lv_test.exs index d50b6f37c..5f452c204 100644 --- a/test/logflare_web/live/search_live/logs_search_lv_test.exs +++ b/test/logflare_web/live/search_live/logs_search_lv_test.exs @@ -498,7 +498,8 @@ defmodule LogflareWeb.Source.SearchLVTest do } }) |> Floki.parse_document!() - |> Floki.find("div[role=alert]>span") == [] + |> Floki.find("div[role=alert]>span") + |> Enum.empty?() end end