View Source Dumper.Config behaviour (Dumper v0.2.5)

Provides sensible defaults for how data should be rendered.

If you'd like your own customizations, define a module that implements the Dumper.Config behaviour:

# An example dumper config module
defmodule MyApp.DumperConfig do
  use Dumper.Config

  def ids_to_schema() do
    %{
      book_id: Library.Book,
      author_id: Library.Author
    }
  end

  def display(%{field: :last_name} = assigns) do
    ~H|<span style="color: red"><%= @value %></span>|
  end

  def custom_record_links(%Library.Book{} = book) do
    [
      {~p"/logging/#{book.id}", "Logs"},
      {"https://goodreads.com/search?q=#{book.title}", "Goodreads"},
    ]
  end
end

Then update the live_dashboard entry in the router.ex file to add the config module:

live_dashboard "/dashboard", additional_pages:
  [dumper: {Dumper.LiveDashboardPage, repo: MyApp.Repo, config_module: MyApp.DumperConfig}]

Implementing each callback provides a different way to control how the data is rendered:

The use Dumper.Config brings in the default definitions of behaviour, so you can choose to define one, all, or none of them. As such, even this is a valid implementation (although it would be functionally the same as not defining a config module at all):

defmodule MyApp.DumperConfig do
  use Dumper.Config
end

Summary

Callbacks

Additional records to be rendered on the given record's page.

A mapping from schema module => list of its fields that will be rendered.

Custom links rendered when viewing a specific record.

Fine-grained control over how any field is rendered. This is a functional component that takes in an assigns map and returns a valid heex expression.

A mapping from schema module => list of its fields that will be excluded from being rendered.

A map of ids (as atoms) to the schema module they should link to.

Callbacks

Link to this callback

additional_associations(record)

View Source
@callback additional_associations(record :: map()) :: Keyword.t()

Additional records to be rendered on the given record's page.

@impl Dumper.Config
def additional_associations(%Book{id: book_id}) do
  # look up reviews on goodreads
  [goodreads_reviews: [top_review, lowest_review]]
end

For a given record, return a keyword list of association name to list of records. Overriding this callback allows you to render more data at the bottom of the page. This is useful when the given record doesn't explicitly define an association, or the data you want to display doesn't live in the database.

Note that while regular associations are paginated, since these are custom we can't automatically paginate them. It's recommended to cap the number of records returned.

@callback allowed_fields() :: nil | map() | {map(), :strict | :lenient}

A mapping from schema module => list of its fields that will be rendered.

Can return

  • nil: all fields in all tables are shown
  • a map: a field is only displayed if the exact Schema + field pairing exists in the map
  • {map, :strict}: a field is only displayed if the exact Schema + field pairing exists in the map
  • {map, :lenient}: a field is displayed if the exact Schema + field pairing exists in the map or the schema module is not present in the map

Here's an example where we return a mapping of schema modules to the fields we want to allow displayed:

@impl Dumper.Config
def allowed_fields() do
  %{
    Patron => [:id, :last_name],
    Book => [:title]
  }
end

In the above example, the returned map defaults to strict mode. Rendered fields would include patron.last_name and book.title. Hidden fields would include fields like patron.first_name and author.last_name.

@impl Dumper.Config
def allowed_fields() do
  map = %{
    Patron => [:id, :last_name],
    Book => [:title]
  }
  {map, :lenient}
end

In the above example, lenient strictness means that author.last_name would now be rendered even though the Author key not explicitly defined in the returned mapping.

It's recommended to at least include the primary key field in the list so that there is at least one field to display.

excluded_fields/0 is ignored if:

  • this callback is implemented and returns strict mode (or returns only a map)
  • this callback is implemented and returns lenient mode, but the schema key is present in the map
Link to this callback

custom_record_links(record)

View Source
@callback custom_record_links(record :: map()) :: [
  {route :: binary(), display_text :: binary()}
]

Custom links rendered when viewing a specific record.

This function takes a record you can pattern match on, and must return a list of {route, text} tuples.

@impl Dumper.Config
def custom_record_links(%Book{} = book) do
  [
    {~p"/logging/#{book.id}", "Logs"},
    {"https://goodreads.com/search?q=#{book.title}", "Goodreads"},
  ]
end

def custom_record_links(%Ticket{} = ticket),
  do: [{"https://jira.com/#{ticket.project}/#{ticket.id}", "Jira"}]

In the above example, any Book record you visit in the Dumper will display two links labelled "Logs" and "Goodreads" at the top of the page. Any Ticket record will likewise display one link, "Jira", in that spot. Routes can be internal or external, verified routes, or plain strings.

Logs, dashboards, traces, support portals, etc are all common use cases, but any {route, text} pair is possible.

@callback display(assigns :: map()) :: Phoenix.LiveView.Rendered.t()

Fine-grained control over how any field is rendered. This is a functional component that takes in an assigns map and returns a valid heex expression.

By default, Dumper has some sensible defaults for how redacted fields, values like true, false, nil, and data types like dates and datetimes are rendered.

It is useful to define as many display/1 function heads as you want, pattern matching on specific values to pick out the specific ones you'd like to customize.

The assigns that is passed in is a map of the following form:

# assigns
%{
  module: Library.Author,
  field: :last_name,
  resource: %Library.Author{ ... }, # the entire ecto struct
  value: "Smith",
  type: :binary, # the Ecto data type
  redacted: false
%}

So for example, if you wanted every last name to be red except for the Author table, which should have blue last names, you could do the following:

@impl Dumper.Config
def display(%{field: :last_name, module: Library.Author} = assigns) do
  ~H|<span style="color: blue"><%= @value %></span>|
end

def display(%{field: :last_name} = assigns) do
  ~H|<span style="color: red"><%= @value %></span>|
end

In this way, you can have near complete control over how a particular field, data type, or entire module is displayed.

Note that LiveDashboard ships with Bootstrap 4.6, so you are free to use Bootstrap classes in your styling to help achieve a consistent look and feel.

@callback excluded_fields() :: map()

A mapping from schema module => list of its fields that will be excluded from being rendered.

@impl Dumper.Config
def excluded_fields() do
  %{
    Library.Patron => [:email_address, :date_of_birth],
    Library.Book => [:purchase_price]
  }
end

In the above example, when displaying any patron record, the email_address and date_of_birth fields will not be rendered. All others fields will be displayed. The email_address and date_of_birth fields will not even be sent down through the display/1 callback. The same for any book record; the purchase_price field will never be rendered. All other schemas are unaffected - for example a Author schema would display all of its fields, even though it is not included in the returned map.

The excluded fields will only hide fields for a module if the module exists as a key in the returned map.

It's recommended to at least include the primary key field in the list so that there is at least one field to display.

See allowed_fields/0 for cases where excluded_fields/0 is ignored.

@callback ids_to_schema() :: map()

A map of ids (as atoms) to the schema module they should link to.

Each key/value pair in the map will automatically render as a clickable link that navigates the user to the dumper page for that specific record.

By default this map is empty.

Example:

def ids_to_schema() do
  %{
    book_id: Library.Book,
    author_id: Library.Author
  }
end

Here, any field in any schema named book_id would render as a link to the Book record, via a Repo.get!(id) call under the hood, instead of just printing the value. This allows you to easily navigate through your data by clicking connected links.