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
1 change: 1 addition & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ spark_locals_without_parens = [
show_sensitive_fields: 1,
table_columns: 1,
table_filterable_columns: 1,
table_searchable_columns: 1,
table_sortable_columns: 1,
type: 1,
update_actions: 1
Expand Down
1 change: 1 addition & 0 deletions assets/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module.exports = {
"./js/**/*.js",
"./../lib/ash_admin/components/**/*.*ex",
"./../lib/ash_admin/pages/**/*.*ex",
"./../lib/ash_admin/themes/**/*.*ex",
"../deps/cinder/**/*.*ex",
],
safelist: ["prose", "dark:prose-invert", "max-w-none", "border-l-4"],
Expand Down
1 change: 1 addition & 0 deletions documentation/dsls/DSL-AshAdmin.Resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Configure the admin dashboard for a given resource.
| [`table_columns`](#admin-table_columns){: #admin-table_columns } | `list(atom)` | | The list of attributes to render on the table view. |
| [`table_sortable_columns`](#admin-table_sortable_columns){: #admin-table_sortable_columns } | `list(atom)` | | The list of columns that can be sorted. If not specified, all columns are sortable. |
| [`table_filterable_columns`](#admin-table_filterable_columns){: #admin-table_filterable_columns } | `list(atom)` | | The list of columns that can be filtered. If not specified, all columns are filterable. |
| [`table_searchable_columns`](#admin-table_searchable_columns){: #admin-table_searchable_columns } | `list(atom)` | | The list of columns that the table search box searches. A search box shows only when this is set. Each column must also be in the table columns. |
| [`format_fields`](#admin-format_fields){: #admin-format_fields } | `list(any)` | | The list of fields and their formats represented as a MFA. For example: `updated_at: {Timex, :format!, ["{0D}-{0M}-{YYYY} {h12}:{m} {AM}"]}`. Datatable pages format all given fields. Show and Update pages format given read-only fields of types `Ash.Type.Date`, `Ash.Type.DateTime`, `Ash.Type.Time`, `Ash.Type.NaiveDatetime`, `Ash.Type.UtcDatetime` and `Ash.Type.UtcDatetimeUsec`. |
| [`relationship_display_fields`](#admin-relationship_display_fields){: #admin-relationship_display_fields } | `list(atom)` | | The list of attributes to render when this resource is shown as a relationship on another resource's datatable. |
| [`resource_group`](#admin-resource_group){: #admin-resource_group } | `atom` | | The group in the top resource dropdown that the resource appears in. |
Expand Down
12 changes: 10 additions & 2 deletions lib/ash_admin/components/resource/data_table.ex
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ defmodule AshAdmin.Components.Resource.DataTable do
>
<div class="shadow-sm ring-1 ring-slate-200 dark:ring-slate-700 overflow-auto sm:rounded-md bg-white dark:bg-slate-900">
<div class="px-2">
<div :if={@ash_query && @has_filters} class="px-6 pt-6">
<div :if={@ash_query && (@has_filters || @has_search)} class="px-6 pt-6">
<button
phx-click="toggle_filters"
phx-target={@myself}
Expand Down Expand Up @@ -117,6 +117,7 @@ defmodule AshAdmin.Components.Resource.DataTable do
label={to_name(field_name)}
filter={filterable?(@resource, field_name)}
sort={sortable?(@resource, field_name)}
search={searchable?(@resource, field_name)}
>
{render_field_value(record, field_name, assigns)}
</:col>
Expand Down Expand Up @@ -330,12 +331,14 @@ defmodule AshAdmin.Components.Resource.DataTable do
{page_size, pagination_mode} = pagination_config(socket.assigns.action)

has_filters = AshAdmin.Resource.table_filterable_columns(socket.assigns.resource) != []
has_search = AshAdmin.Resource.table_searchable_columns(socket.assigns.resource) != []

socket =
assign(socket,
page_size: page_size,
pagination_mode: pagination_mode,
has_filters: has_filters
has_filters: has_filters,
has_search: has_search
)

{:ok,
Expand Down Expand Up @@ -568,6 +571,11 @@ defmodule AshAdmin.Components.Resource.DataTable do
end
end

# Check if a field is searched by the table search box
defp searchable?(resource, field_name) do
field_name in AshAdmin.Resource.table_searchable_columns(resource)
end

# Check if field is an actual resource attribute (not a relationship or calculated field)
defp has_attribute?(resource, field_name) do
Ash.Resource.Info.field(resource, field_name) != nil
Expand Down
9 changes: 9 additions & 0 deletions lib/ash_admin/resource/resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ defmodule AshAdmin.Resource do
doc:
"The list of columns that can be filtered. If not specified, all columns are filterable."
],
table_searchable_columns: [
type: {:list, :atom},
doc:
"The list of columns that the table search box searches. A search box shows only when this is set. Each column must also be in the table columns."
],
format_fields: [
type: {:list, :any},
doc: """
Expand Down Expand Up @@ -286,6 +291,10 @@ defmodule AshAdmin.Resource do
Spark.Dsl.Extension.get_opt(resource, [:admin], :table_filterable_columns, nil, true)
end

def table_searchable_columns(resource) do
Spark.Dsl.Extension.get_opt(resource, [:admin], :table_searchable_columns, [], true)
end

defp find_polymorphic_tables(resource, domains) do
domains
|> Enum.flat_map(&AshAdmin.Domain.show_resources/1)
Expand Down
6 changes: 6 additions & 0 deletions lib/ash_admin/themes/ash_admin_theme.ex
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ defmodule AshAdmin.Themes.AshAdminTheme do
set :filter_clear_button_class,
"ml-2 text-slate-400 dark:text-slate-500 hover:text-slate-600 dark:hover:text-slate-300 text-sm"

# Search box - left padding leaves room for the magnifier icon
set :search_input_class,
"w-72 pl-9 pr-3 py-2 border border-slate-300 dark:border-slate-600 rounded text-sm bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 focus:ring-1 focus:ring-slate-500 focus:border-slate-500"

set :search_icon_class, "h-4 w-4 text-slate-400 dark:text-slate-500"

# Input styling - larger inputs with thicker borders to match table
set :filter_text_input_class,
"w-48 px-3 py-2 border border-slate-300 dark:border-slate-600 rounded text-sm bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 focus:ring-1 focus:ring-slate-500 focus:border-slate-500"
Expand Down
57 changes: 57 additions & 0 deletions test/components/resource/data_table_search_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# SPDX-FileCopyrightText: 2020 ash_admin contributors <https://github.com/ash-project/ash_admin/graphs/contributors>
#
# SPDX-License-Identifier: MIT

defmodule AshAdmin.Test.Components.Resource.DataTableSearchTest do
use ExUnit.Case, async: false

import Phoenix.ConnTest
import Phoenix.LiveViewTest

@endpoint AshAdmin.Test.Endpoint

setup do
Ash.create!(AshAdmin.Test.Blog, %{body: "Apple pie"})
Ash.create!(AshAdmin.Test.Blog, %{body: "Banana bread"})

%{conn: build_conn()}
end

test "the search box filters rows on the searchable columns", %{conn: conn} do
{:ok, view, _html} = live(conn, "/api/admin?domain=Domain&resource=Blog&action_type=read")

html = render_async(view)
assert html =~ "Apple pie"
assert html =~ "Banana bread"

# The search box is hidden with the filters until the Filters button is clicked
refute html =~ ~s(name="search")

view
|> element("button[phx-click=toggle_filters]")
|> render_click()

assert render_async(view) =~ ~s(name="search")

view
|> element("form[phx-change=filter_change]")
|> render_change(%{"search" => "banana"})

html = render_async(view)
refute html =~ "Apple pie"
assert html =~ "Banana bread"
end

test "a table without searchable columns has no search box", %{conn: conn} do
{:ok, view, _html} =
live(conn, "/api/admin?domain=ManyToMany&resource=Profile&action_type=read")

render_async(view)

view
|> element("button[phx-click=toggle_filters]")
|> render_click()

refute render_async(view) =~ ~s(name="search")
end
end
6 changes: 6 additions & 0 deletions test/support/resources/blog.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ defmodule AshAdmin.Test.Blog do
end
end

actions do
defaults([:read, create: [:body]])
end

admin do
resource_group(:group_b)
table_searchable_columns([:body])
table_filterable_columns([])
end
end
Loading