improvement: flatten search results

This commit is contained in:
Zach Daniel 2022-07-21 17:11:21 -04:00
parent 48bd736190
commit 3535896420
4 changed files with 95 additions and 188 deletions

View file

@ -7,6 +7,13 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.Changes.RenderMarkdown do
source = Ash.Changeset.get_attribute(changeset, opts[:source])
text = remove_ash_hq_hidden_content(source)
changeset =
if text != source do
Ash.Changeset.force_change_attribute(changeset, opts[:source], text)
else
changeset
end
case AshHq.Docs.Extensions.RenderMarkdown.as_html(
text,
AshHq.Docs.Extensions.RenderMarkdown.header_ids?(changeset.resource)

View file

@ -6,6 +6,7 @@ defmodule AshHq.Docs.Search do
argument :query, :string do
allow_nil?(false)
constraints trim?: false, allow_empty?: true
end
argument :library_versions, {:array, :uuid} do

View file

@ -2,120 +2,16 @@ defmodule AshHq.Docs.Search.Steps.BuildResults do
use Ash.Flow.Step
def run(input, _opts, _context) do
search_results =
input
|> Map.values()
|> List.flatten()
|> Enum.sort_by(
# false comes first, and we want all things where the name matches to go first
&{name_match_rank(&1), -&1.match_rank, Map.get(&1, :extension_order, -1),
Enum.count(Map.get(&1, :path, []))}
)
sort_rank =
search_results
|> Enum.with_index()
|> Map.new(fn {item, i} ->
{item.id, i}
end)
results =
search_results
|> Enum.group_by(fn
%{extension_type: type} ->
type
%AshHq.Docs.Function{module_name: module_name} ->
module_name
%AshHq.Docs.Module{name: name} ->
name
%AshHq.Docs.Guide{
library_version: %{version: version, library_display_name: library_display_name}
} ->
"#{library_display_name} #{version}"
%AshHq.Docs.Extension{
library_version: %{version: version, library_display_name: library_display_name}
} ->
"#{library_display_name} #{version}"
%AshHq.Docs.LibraryVersion{library_display_name: library_display_name, version: version} ->
"#{library_display_name} #{version}"
other ->
raise "Nothing matching #{inspect(other)}"
end)
|> Enum.sort_by(fn {_type, items} ->
items
|> Enum.map(&Map.get(sort_rank, &1.id))
|> Enum.min()
end)
|> Enum.map(fn {type, items} ->
{type, group_by_paths(items, sort_rank)}
end)
item_list = item_list(results)
{:ok, %{item_list: item_list, results: results}}
end
defp item_list(results) do
List.flatten(do_item_list(results))
end
defp do_item_list({_key, %{items: items, further: further}}) do
do_item_list(items) ++ do_item_list(further)
end
defp do_item_list(items) when is_list(items) do
Enum.map(items, &do_item_list/1)
end
defp do_item_list(item), do: item
defp group_by_paths(items, sort_rank) do
items
|> Enum.map(&{Map.get(&1, :path, []), &1})
|> do_group_by_paths(sort_rank)
end
defp do_group_by_paths(items, sort_rank, path_acc \\ []) do
{items_for_group, further} =
Enum.split_with(items, fn
{[], _} ->
true
_ ->
false
end)
further_items =
further
|> Enum.group_by(
fn {[next | _rest], _item} ->
next
end,
fn {[_next | rest], item} ->
{rest, item}
end
)
|> Enum.sort_by(fn {_nested, items} ->
items
|> Enum.map(&elem(&1, 1))
|> Enum.sort_by(&Map.get(sort_rank, &1.id))
end)
|> Enum.map(fn {nested, items} ->
{nested, do_group_by_paths(items, sort_rank, path_acc ++ [nested])}
end)
items =
items_for_group
|> Enum.map(&elem(&1, 1))
|> Enum.sort_by(&Map.get(sort_rank, &1.id))
%{path: path_acc, items: items, further: further_items}
# search_results =
{:ok,
input
|> Map.values()
|> List.flatten()
|> Enum.sort_by(
# false comes first, and we want all things where the name matches to go first
&{name_match_rank(&1), -&1.match_rank, Map.get(&1, :extension_order, -1),
Enum.count(Map.get(&1, :path, []))}
)}
end
defp name_match_rank(record) do

View file

@ -4,22 +4,23 @@ defmodule AshHqWeb.Components.Search do
require Ash.Query
alias AshHqWeb.Routes
alias AshHqWeb.Components.CalloutText
alias Surface.Components.{Form, LiveRedirect}
alias Surface.Components.Form.{Checkbox, Label, Select}
prop(open, :boolean, default: false)
prop(close, :event, required: true)
prop(libraries, :list, required: true)
prop(selected_versions, :map, required: true)
prop(change_versions, :event, required: true)
prop(selected_types, :list, required: true)
prop(change_types, :event, required: true)
prop(uri, :string, required: true)
prop open, :boolean, default: false
prop close, :event, required: true
prop libraries, :list, required: true
prop selected_versions, :map, required: true
prop change_versions, :event, required: true
prop selected_types, :list, required: true
prop change_types, :event, required: true
prop uri, :string, required: true
data(versions, :map, default: %{})
data(search, :string, default: "")
data(results, :map, default: %{})
data(selected_item, :string)
data versions, :map, default: %{}
data search, :string, default: ""
# data(results, :map, default: %{})
data selected_item, :string
def render(assigns) do
~F"""
@ -94,7 +95,7 @@ defmodule AshHqWeb.Components.Search do
</Form>
</div>
<div class="pl-4 overflow-y-auto col-span-6 md:col-span-7 xl:col-span-8">
{render_groups(assigns, @results)}
{render_items(assigns, @item_list)}
</div>
</div>
</div>
@ -103,72 +104,73 @@ defmodule AshHqWeb.Components.Search do
"""
end
defp render_groups(assigns, results, first? \\ true) do
defp render_items(assigns, items) do
~F"""
{#for {group, results} <- results}
<div class={"ml-4": !first?}>
{#if first?}
<div class="font-medium text-lg">
{group}
{#for item <- items}
<LiveRedirect to={Routes.doc_link(item, @selected_versions)} opts={id: item.id}>
<div class={
"rounded-lg mb-4 py-2 px-2 hover:bg-gray-400 dark:hover:bg-gray-600",
"bg-gray-400 dark:bg-gray-600": @selected_item.id == item.id,
"bg-gray-200 dark:bg-gray-800": @selected_item.id != item.id
}>
<div class="flex justify-between pb-2">
<div class="flex flex-row">
{#for path_item <- item_path(item)}
<Heroicons.Solid.ChevronRightIcon class="h-6 w-6" />
<div>
{path_item}
</div>
{/for}
<Heroicons.Solid.ChevronRightIcon class="h-6 w-6" />
<div class="font-bold text-lg">
{#if item.name_matches}
<CalloutText>{item_name(item)}</CalloutText>
{#else}
{item_name(item)}
{/if}
</div>
</div>
<div>
{item_type(item)}
</div>
</div>
{/if}
{#if Enum.empty?(results.items)}
{render_results(assigns, results)}
{#else}
<div class={"mt-4", "border-l border-gray-700 pl-2": !first?}>
{render_results(assigns, results)}
<div class="text-gray-700 dark:text-gray-400">
{raw(item.search_headline)}
</div>
{/if}
</div>
</div>
</LiveRedirect>
{/for}
"""
end
defp render_results(assigns, results) do
~F"""
<div>
<div class="font-medium mb-1">
{#if Map.get(results, :path, []) != []}
<div class="flex flex-row justify-start align-middle items-center text-center">
{#for path_item <- Map.get(results, :path, [])}
<Heroicons.Solid.ChevronRightIcon class="h-6 w-6" />
<div>
{path_item}
</div>
{/for}
</div>
{/if}
</div>
{#for item <- results.items}
<LiveRedirect to={Routes.doc_link(item, @selected_versions)} opts={id: item.id}>
<div class={
"rounded-lg mb-4 py-2 px-2 hover:bg-gray-400 dark:hover:bg-gray-600",
"bg-gray-400 dark:bg-gray-600": @selected_item.id == item.id,
"bg-gray-200 dark:bg-gray-800": @selected_item.id != item.id
}>
<div class="flex justify-between pb-2">
<div>
{#if item.__struct__ != AshHq.Docs.LibraryVersion &&
item.name != List.last(Map.get(results, :path, []))}
{item.name}
{/if}
{#if item.__struct__ == AshHq.Docs.LibraryVersion}
{item.version}
{/if}
</div>
<div>
{item_type(item)}
</div>
</div>
<div class="text-gray-700 dark:text-gray-400">
{raw(item.search_headline)}
</div>
</div>
</LiveRedirect>
{/for}
{render_groups(assigns, results.further, false)}
</div>
"""
defp item_name(%{name: name}), do: name
defp item_name(%{version: version}), do: version
defp item_path(%{
library_name: library_name,
extension_name: extension_name,
path: path
}) do
[library_name, extension_name, path] |> List.flatten()
end
defp item_path(%{
library_name: library_name,
module_name: module_name
}) do
[library_name, module_name]
end
defp item_path(%{library_name: library_name}) do
[library_name]
end
defp item_path(%{library_version: %{library_name: library_name}}) do
[library_name]
end
defp item_path(_) do
[]
end
def mount(socket) do
@ -252,7 +254,8 @@ defmodule AshHqWeb.Components.Search do
end)
|> Enum.reject(&is_nil/1)
%{results: results, item_list: item_list} =
# %{results: results, item_list: item_list} =
item_list =
AshHq.Docs.Search.run!(
socket.assigns.search,
versions,
@ -262,7 +265,7 @@ defmodule AshHqWeb.Components.Search do
selected_item = Enum.at(item_list, 0)
socket
|> assign(:results, results)
# |> assign(:results, results)
|> assign(:item_list, item_list)
|> set_selected_item(selected_item)
end