Skip to content
Open
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
6 changes: 6 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,9 @@ config :ex_aws, :s3,

# Define the base host to use
config :admin, :base_host, "localhost:3114"

# Define the backend host to use
config :admin, :backend_host, "localhost:3000"

# Publication index (development defaults)
config :admin, :publication_reindex_headers, [{"meilisearch-rebuild", "secret"}]
3 changes: 3 additions & 0 deletions config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@ config :admin, :logger, [

# Runtime production configuration, including reading
# of environment variables, is done on config/runtime.exs.

config :admin,
publication_reindex_headers: {"meilisearch-rebuild", System.get_env("PUBLICATION_INDEX_HEADER_VALUE")}
1 change: 1 addition & 0 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ if config_env() == :prod do
end

config :admin, base_host: base_host
config :admin, backend_host: base_host
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure here.
In local I used localhost:3000 that automatically redirects to /api.
Using api.graasp.org in prod would work as it redirects to /api too, but using graasp.org would require /api (so it's not a host anymore).


# Config the File Items bucket name
config :admin, :file_items_bucket, System.get_env("FILE_ITEMS_BUCKET_NAME", "file-items")
Expand Down
76 changes: 76 additions & 0 deletions lib/admin/publications/search_index.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
defmodule Admin.Publications.SearchIndex do
@moduledoc """
Utilities to trigger a publication reindex on an external indexing endpoint.

It expects the following application config in `:admin`:

config :admin, :publications,
publication_index_url
publication_index_header_value

"""

require Logger

defp http_client, do: Application.get_env(:admin, :publication_index_http_client, Req)

@doc """
Trigger a reindex by calling the configured endpoint with the configured header.

Returns `{:ok, %Req.Response{}}` on success or `{:error, error_code}` on failure.
"""
def reindex do
with {:ok, client, url, headers} <- build_reindex_request() do

req = client.new(method: :get, url: url, headers: headers)

IO.puts("Reindex request: #{inspect(req)}")

case client.request(req) do
%Req.Response{} = resp ->
if resp.status in 200..299 do
{:ok, resp}
else
Logger.error("SearchIndex.reindex failed: #{inspect(resp)}")
{:error, resp.status}
end

{:ok, %Req.Response{} = resp} ->
if resp.status in 200..299 do
{:ok, resp}
else
Logger.error("SearchIndex.reindex failed: #{inspect(resp)}")
{:error, resp.status}
end

{:error, reason} ->
Logger.error("SearchIndex.reindex failed: #{inspect(reason)}")
{:error, 500}

other ->
Logger.error("SearchIndex.reindex unexpected response: #{inspect(other)}")
{:error, :unexpected_response}
end
end
end

@doc false
# Build the HTTP client, url, and headers for the reindex request.
defp build_reindex_request do

case Application.get_env(:admin, :backend_host) do
nil ->
{:error, :missing_publication_index_url}

url ->
case Application.get_env(:admin, :publication_reindex_headers) do
:error ->
{:error, :missing_publication_index_header_value}

headers_value ->
client = http_client()
{:ok, client, "http://#{url}/items/collections/search/rebuild", headers_value}
end
end
end
end
4 changes: 4 additions & 0 deletions lib/admin_web/components/layouts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ defmodule AdminWeb.Layouts do
<ul class="p-2">
<li><.link navigate={~p"/published_items"}>Recent</.link></li>
<li><.link navigate={~p"/published_items/featured"}>Featured</.link></li>
<li><.link navigate={~p"/published_items/search_index"}>Search Index</.link></li>
</ul>
</li>
<li><.link navigate={~p"/publishers"}>Apps</.link></li>
Expand Down Expand Up @@ -212,6 +213,9 @@ defmodule AdminWeb.Layouts do
<li>
<.link navigate={~p"/published_items/featured"}>Featured</.link>
</li>
<li>
<.link navigate={~p"/published_items/search_index"}>Search Index</.link>
</li>
</ul>
</details>
</li>
Expand Down
59 changes: 59 additions & 0 deletions lib/admin_web/live/publication_search_index_live.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
defmodule AdminWeb.PublicationSearchIndexLive do
use AdminWeb, :live_view

alias Admin.Publications.SearchIndex

@impl true
def render(assigns) do
~H"""
<Layouts.admin flash={@flash} current_scope={@current_scope}>
<div class="p-6">
<.header>
Index Status
<:subtitle>
You can start a reindex of the library index. This is useful if your search engine is not up to date.
</:subtitle>
</.header>
<div role="alert" class="alert alert-warning">
<.icon name="hero-exclamation-triangle" />
<span>This operation is heavy on the database and might take some time to complete.</span>
</div>
<p class="my-4">{@status || ""}</p>
<div>
<button
phx-click="reindex"
class="px-4 py-2 disabled:bg-gray-600 bg-blue-600 text-white rounded"
disabled={@indexing}
>
Start Reindex
</button>
</div>
</div>
</Layouts.admin>
"""
end

@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, status: nil, indexing: false)}
end

@impl true
def handle_event("reindex", _params, socket) do
case SearchIndex.reindex() do
{:ok, _resp} ->
{:noreply,
assign(socket,
indexing: true,
status: "Reindex has started. It might take some time to complete."
)}

{:error, reason} ->
{:noreply,
assign(socket,
indexing: false,
status: "Reindex failed with error #{inspect(reason)}"
)}
end
end
end
1 change: 1 addition & 0 deletions lib/admin_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ defmodule AdminWeb.Router do

# published_items
live "/published_items/:id/unpublish", PublishedItemLive.Unpublish, :unpublish
live "/published_items/search_index", PublicationSearchIndexLive, :index

# apps
scope "/apps" do
Expand Down
84 changes: 84 additions & 0 deletions test/admin/publications/publication_search_index_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
defmodule Admin.Publications.PublicationSearchIndexTest do
use ExUnit.Case, async: true

alias Admin.Publications.SearchIndex

setup do
# ensure we start with a clean client config
Application.delete_env(:admin, :publication_index_http_client)
:ok
end

test "returns error when url is missing" do
Application.delete_env(:admin, :publication_index_url)
Application.put_env(:admin, :publication_index_header_value, "token")

assert {:error, :missing_publication_index_url} = SearchIndex.reindex()
end

test "returns error when header value is missing" do
Application.put_env(:admin, :publication_index_url, "http://example")
Application.delete_env(:admin, :publication_index_header_value)

assert {:error, :missing_publication_index_header_value} = SearchIndex.reindex()
end

test "returns ok on 2xx response" do
Application.put_env(:admin, :publication_index_url, "http://example")
Application.put_env(:admin, :publication_index_header_value, "token")

client =
Module.concat([__MODULE__, :SuccessClient])

defmodule client do
def new(_opts), do: :req_request

def request(_req) do
resp = %Req.Response{status: 200, body: "ok"}
{:ok, resp}
end
end

Application.put_env(:admin, :publication_index_http_client, client)

assert {:ok, resp} = SearchIndex.reindex()
assert resp.status == 200
end

test "returns error on non-2xx response" do
Application.put_env(:admin, :publication_index_url, "http://example")
Application.put_env(:admin, :publication_index_header_value, "token")

client = Module.concat([__MODULE__, :BadResponseClient])

defmodule client do
def new(_opts), do: :req_request

def request(_req) do
resp = %Req.Response{status: 500, body: "nope"}
{:ok, resp}
end
end

Application.put_env(:admin, :publication_index_http_client, client)

assert {:error, 500} = SearchIndex.reindex()
end

test "returns error tuple when client errors" do
Application.put_env(:admin, :publication_index_url, "http://example")
Application.put_env(:admin, :publication_index_header_value, "token")

client = Module.concat([__MODULE__, :ErrClient])

defmodule client do
def new(_opts), do: :req_request

def request(_req), do: {:error, :econnrefused}
end

Application.put_env(:admin, :publication_index_http_client, client)

assert {:error, :econnrefused} = SearchIndex.reindex()
end
end
56 changes: 56 additions & 0 deletions test/admin_web/live/publication_index_live_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
defmodule AdminWeb.PublicationIndexLiveTest do
use AdminWeb.ConnCase, async: true

import Phoenix.LiveViewTest

setup %{conn: conn} do
%{conn: conn} = register_and_log_in_user(%{conn: conn})
:ok
{:ok, conn: conn}
end

test "shows error when reindex fails", %{conn: conn} do
Application.put_env(:admin, :publication_index_url, "http://example")
Application.put_env(:admin, :publication_index_header_value, "token")

client = Module.concat([__MODULE__, :FailClient])

defmodule client do
def new(_opts), do: :req_request
def request(_req), do: {:error, :econnrefused}
end

Application.put_env(:admin, :publication_index_http_client, client)

{:ok, view, _html} = live(conn, "/publications/reindex")

view |> element("button", "Start Reindex") |> render_click()

assert render(view) =~ "Reindex failed"
end

test "show waitingstatus on success", %{conn: conn} do
Application.put_env(:admin, :publication_index_url, "http://example")
Application.put_env(:admin, :publication_index_header_value, "token")

client = Module.concat([__MODULE__, :OkClient])

defmodule client do
def new(_opts), do: :req_request
def request(_req) do
resp = %Req.Response{status: 200, body: "ok"}
{:ok, resp}
end
end

Application.put_env(:admin, :publication_index_http_client, client)

{:ok, view, _html} = live(conn, "/publications/reindex")

view |> element("button", "Start Reindex") |> render_click()

refute render(view) =~ "Reindex failed"

assert view |> element("button[disabled]", "Start Reindex") |> has_element?()
end
end
Loading