feat: allow to filter events by local-only

In addition to internal (self + federated) and global (global external search engine), introduce the
self possibility

Closes #1322

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2024-02-09 10:54:00 +01:00
parent d1b1979ee5
commit 9d99684402
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
9 changed files with 87 additions and 8 deletions

View file

@ -1,2 +1,2 @@
elixir 1.15.5-otp-26
erlang 26.0.2
erlang 26.2.2
elixir 1.16.1-otp-26

View file

@ -56,7 +56,8 @@ defmodule Mobilizon.GraphQL.API.Search do
minimum_visibility: Map.get(args, :minimum_visibility, :public),
current_actor_id: Map.get(args, :current_actor_id),
exclude_my_groups: Map.get(args, :exclude_my_groups, false),
exclude_stale_actors: true
exclude_stale_actors: true,
local_only: Map.get(args, :search_target, :internal) == :self
],
page,
limit
@ -94,7 +95,13 @@ defmodule Mobilizon.GraphQL.API.Search do
{:ok, service.search_events(Keyword.new(args, fn {k, v} -> {k, v} end))}
else
{:ok, Events.build_events_for_search(Map.put(args, :term, term), page, limit)}
results =
args
|> Map.put(:term, term)
|> Map.put(:local_only, Map.get(args, :search_target, :internal) == :self)
|> Events.build_events_for_search(page, limit)
{:ok, results}
end
end
end

View file

@ -160,6 +160,8 @@ defmodule Mobilizon.GraphQL.Schema.SearchType do
end
enum :search_target do
value(:self, description: "Search only on content from this instance")
value(:internal,
description: "Search on content from this instance and from the followed instances"
)

View file

@ -525,6 +525,7 @@ defmodule Mobilizon.Actors do
Keyword.get(options, :radius),
Keyword.get(options, :bbox)
)
|> filter_by_local_only(Keyword.get(options, :local_only, false))
|> actors_for_location(Keyword.get(options, :location), Keyword.get(options, :radius))
|> events_for_bounding_box(Keyword.get(options, :bbox))
|> filter_by_type(Keyword.get(options, :actor_type, :Group))
@ -1418,6 +1419,13 @@ defmodule Mobilizon.Actors do
defp maybe_join_address(query, _location, _radius, _bbox), do: query
@spec filter_by_local_only(Ecto.Queryable.t(), boolean()) :: Ecto.Query.t()
defp filter_by_local_only(query, true) do
where(query, [q], is_nil(q.domain))
end
defp filter_by_local_only(query, false), do: query
@spec actors_for_location(Ecto.Queryable.t(), String.t(), integer()) :: Ecto.Query.t()
defp actors_for_location(query, location, radius)
when is_valid_string(location) and not is_nil(radius) do

View file

@ -581,6 +581,7 @@ defmodule Mobilizon.Events do
|> events_for_bounding_box(args)
|> filter_online(args)
|> filter_draft()
|> filter_local(if Map.get(args, :local_only, nil) == true, do: true, else: nil)
|> filter_local_or_from_followed_instances_events()
|> filter_public_visibility()
|> event_order(Map.get(args, :sort_by, :match_desc), search_string)

View file

@ -293,6 +293,7 @@ export enum InstanceFollowStatus {
}
export enum SearchTargets {
SELF = "SELF",
INTERNAL = "INTERNAL",
GLOBAL = "GLOBAL",
}

View file

@ -68,6 +68,22 @@
<fieldset class="flex flex-col">
<legend class="sr-only">{{ t("Search target") }}</legend>
<div>
<input
id="selfTarget"
v-model="searchTarget"
type="radio"
name="selfTarget"
:value="SearchTargets.SELF"
class="w-4 h-4 border-gray-300 focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-600 dark:focus:bg-blue-600 dark:bg-gray-700 dark:border-gray-600"
/>
<label
for="selfTarget"
class="ml-3 font-medium text-gray-900 dark:text-gray-300"
>{{ t("From this instance only") }}</label
>
</div>
<div>
<input
id="internalTarget"

View file

@ -55,7 +55,8 @@ defmodule Mobilizon.GraphQL.API.SearchTest do
minimum_visibility: :public,
current_actor_id: nil,
exclude_my_groups: false,
exclude_stale_actors: true
exclude_stale_actors: true,
local_only: false
],
1,
10
@ -72,7 +73,7 @@ defmodule Mobilizon.GraphQL.API.SearchTest do
assert {:ok, %{total: 1, elements: [%Event{title: "super toto event"}]}} =
Search.search_events(%{term: "toto"}, 1, 10)
assert_called(Events.build_events_for_search(%{term: "toto"}, 1, 10))
assert_called(Events.build_events_for_search(%{term: "toto", local_only: false}, 1, 10))
end
end
end

View file

@ -3,6 +3,9 @@ defmodule Mobilizon.GraphQL.Resolvers.SearchTest do
import Mobilizon.Factory
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.Event
alias Mobilizon.Federation.ActivityPub.Relay
alias Mobilizon.Service.Workers
alias Mobilizon.GraphQL.AbsintheHelpers
@ -15,8 +18,8 @@ defmodule Mobilizon.GraphQL.Resolvers.SearchTest do
describe "search events/3" do
@search_events_query """
query SearchEvents($location: String, $radius: Float, $tags: String, $term: String, $beginsOn: DateTime, $endsOn: DateTime) {
searchEvents(location: $location, radius: $radius, tags: $tags, term: $term, beginsOn: $beginsOn, endsOn: $endsOn) {
query SearchEvents($location: String, $radius: Float, $tags: String, $term: String, $beginsOn: DateTime, $endsOn: DateTime, $searchTarget: SearchTarget) {
searchEvents(location: $location, radius: $radius, tags: $tags, term: $term, beginsOn: $beginsOn, endsOn: $endsOn, searchTarget: $searchTarget) {
total,
elements {
id
@ -218,6 +221,46 @@ defmodule Mobilizon.GraphQL.Resolvers.SearchTest do
assert res["errors"] == nil
assert res["data"]["searchEvents"]["total"] == 0
end
test "finds events for the correct target", %{conn: conn} do
event1 = insert(:event, title: "A local event")
%Actor{id: remote_instance_actor_id} = remote_instance_actor = insert(:instance_actor)
%Actor{id: remote_actor_id} = insert(:actor, domain: "somedomain.tld", user: nil)
%Event{url: remote_event_url} =
event2 = insert(:event, local: false, title: "My Remote event")
Mobilizon.Share.create(remote_event_url, remote_instance_actor_id, remote_actor_id)
%Actor{} = own_instance_actor = Relay.get_actor()
insert(:follower, target_actor: remote_instance_actor, actor: own_instance_actor)
Workers.BuildSearch.insert_search_event(event1)
Workers.BuildSearch.insert_search_event(event2)
res =
AbsintheHelpers.graphql_query(conn,
query: @search_events_query,
variables: %{term: "event"}
)
assert res["errors"] == nil
assert res["data"]["searchEvents"]["total"] == 2
elements = res["data"]["searchEvents"]["elements"]
assert MapSet.new(Enum.map(elements, & &1["id"])) ==
MapSet.new([to_string(event1.id), to_string(event2.id)])
res =
AbsintheHelpers.graphql_query(conn,
query: @search_events_query,
variables: %{term: "event", searchTarget: "SELF"}
)
assert res["errors"] == nil
assert res["data"]["searchEvents"]["total"] == 1
end
end
describe "search_persons/3" do