Skip to content

Conversation

@tomerqodo
Copy link

@tomerqodo tomerqodo commented Dec 4, 2025

User description

Benchmark PR plausible#5874

Type: Clean (correct implementation)

Original PR Title: Updated empty states across settings
Original PR Description: ### Changes

  • All empty states have been enhanced by center-aligned copy, link to docs, and CTA.
  • Whenever empty state is shown, the setting's subtitle is hidden.

Tests

  • Automated tests have been added

Dark mode


PR Type

Enhancement, Tests


Description

  • Redesigned empty states across settings pages with center-aligned copy, documentation links, and call-to-action buttons

  • Enhanced empty state visibility by conditionally hiding setting subtitles when no content exists

  • Refactored component architecture to use current_user parameter instead of current_role for improved context awareness

  • Implemented new .tile component wrapper with feature toggle support for goals, props, funnels, and imports/exports settings

  • Added new toggle_live component for rendering feature toggles in LiveView contexts with state management

  • Updated empty state messaging across multiple settings: sites, goals, properties, funnels, shields (IP, hostname, page, country rules), shared links, API keys, and plugin tokens

  • Implemented conditional filter bar rendering that only displays when searching or content exists

  • Extracted empty state and search result components into separate, reusable modules

  • Updated comprehensive test coverage for all modified settings pages and components with new empty state assertions


Diagram Walkthrough

flowchart LR
  A["Settings Pages<br/>goals, props, funnels,<br/>shields, etc."] -->|"Refactored with<br/>tile wrapper"| B["Tile Component<br/>with feature toggle"]
  A -->|"Updated empty<br/>state messaging"| C["Enhanced Empty States<br/>centered layout,<br/>docs links, CTAs"]
  D["Components<br/>current_role param"] -->|"Refactored to"| E["current_user param<br/>for better context"]
  F["Filter bars &<br/>content sections"] -->|"Conditional<br/>rendering"| G["Show only when<br/>searching or<br/>content exists"]
  H["Feature Toggles"] -->|"New component"| I["toggle_live<br/>LiveView component"]
  C -->|"Tested with"| J["Updated Test Suite<br/>new assertions &<br/>messaging"]
Loading

File Walkthrough

Relevant files
Tests
12 files
site_controller_test.exs
Enhanced empty state test coverage for sites                         

test/plausible_web/controllers/site_controller_test.exs

  • Enhanced empty state tests with more descriptive test names and
    assertions for personal vs team sites
  • Added tests for different empty state scenarios: personal sites, team
    sites, and search results
  • Updated assertions to check for new empty state messaging like "Add
    your first personal site" and "Add your first team site"
  • Updated search result message from "Please search for something else"
    to "Try a different search term"
+73/-122
props_settings_test.exs
Updated props settings tests for new empty state                 

test/plausible_web/live/props_settings_test.exs

  • Updated assertions to check for new empty state message "Create a
    custom property" instead of old message
  • Added setup of properties in tests where search input is expected to
    render
  • Added new test for dashboard toggle functionality for props feature
  • Removed commented-out test descriptions
+18/-13 
notice_test.exs
Updated billing notice tests to use current_user parameter

test/plausible_web/components/billing/notice_test.exs

  • Changed parameter from current_role to current_user in all test cases
  • Updated test setup to use actual user objects instead of role atoms
  • Added proper team member setup for role-based tests
+20/-19 
goal_settings_test.exs
Updated goal settings tests for new empty state messaging

test/plausible_web/live/goal_settings_test.exs

  • Updated assertions to check for new empty state message "Create your
    first goal"
  • Added setup of goals in tests where search input is expected to render
  • Added new test for dashboard toggle functionality for goals feature
+14/-3   
billing_test.exs
Updated billing component tests to use current_user parameter

test/plausible_web/components/billing/billing_test.exs

  • Changed parameter from current_role to current_user in all test cases
  • Updated test setup to use actual user objects instead of role atoms
  • Added proper team member setup for role-based tests
+15/-9   
funnel_settings_test.exs
Updated funnel settings tests for new empty state and toggle

test/plausible_web/live/funnel_settings_test.exs

  • Updated test to call setup_funnels instead of setup_goals for search
    input test
  • Updated assertion text from "Set up a few goals first" to "Set up a
    few goals"
  • Added new test for dashboard toggle functionality for funnels feature
+12/-2   
sites_test.exs
Updated sites list tests for new empty state messaging     

test/plausible_web/live/sites_test.exs

  • Updated assertions to check for new empty state messages like "Add
    your first personal site"
  • Updated assertions to check for "Go to team sites" instead of "Go to
    your team"
  • Removed checks for old empty state messages
+5/-5     
hostnames_test.exs
Updated hostname shields tests for new empty state messaging

test/plausible_web/live/shields/hostnames_test.exs

  • Updated assertion to check for new empty state message "Allow a
    hostname"
  • Updated assertion to check for new description text about traffic
    recording
+2/-2     
ip_addresses_test.exs
Updated IP shields tests for new empty state messaging     

test/plausible_web/live/shields/ip_addresses_test.exs

  • Updated assertion to check for new empty state message "Block an IP
    address"
+1/-1     
countries_test.exs
Updated country shields tests for new empty state messaging

test/plausible_web/live/shields/countries_test.exs

  • Updated assertion to check for new empty state message "Block a
    country"
+1/-1     
pages_test.exs
Updated page shields tests for new empty state messaging 

test/plausible_web/live/shields/pages_test.exs

  • Updated assertion to check for new empty state message "Block a page"
+1/-1     
shared_link_settings_test.exs
Updated shared link settings tests for new empty state messaging

test/plausible_web/live/shared_link_settings_test.exs

  • Updated assertion to check for new empty state message "Create your
    first shared link"
+1/-1     
Enhancement
24 files
imports_exports_settings.ex
Redesigned imports empty state with tile wrapper                 

lib/plausible_web/live/imports_exports_settings.ex

  • Wrapped import section in .tile component with proper documentation
    and feature handling
  • Restructured empty state to show centered heading, description, and
    CTA buttons when no imports exist
  • Moved import buttons and table into conditional blocks based on
    whether imports exist
  • Updated empty state message from "There are no imports yet" to "Import
    your first data"
+127/-90
ip_rules.ex
Implemented centered empty state for IP rules                       

lib/plausible_web/live/shields/ip_rules.ex

  • Wrapped IP rules section in conditional to show centered empty state
    when no rules exist
  • Added empty state with heading "Block an IP address" and description
    with documentation link
  • Moved filter bar, notice, and table into else block to only show when
    rules exist
  • Updated subtitle to conditionally display only when rules are present
+105/-81
hostname_rules.ex
Implemented centered empty state for hostname rules           

lib/plausible_web/live/shields/hostname_rules.ex

  • Added conditional rendering for empty state with centered layout and
    CTA button
  • Updated subtitle to only display when hostname rules exist
  • Moved filter bar, notice, and table into else block for non-empty
    state
  • Enhanced empty state messaging with "Allow a hostname" heading and
    documentation link
+100/-76
page_rules.ex
Implemented centered empty state for page rules                   

lib/plausible_web/live/shields/page_rules.ex

  • Added conditional rendering for empty state with centered layout and
    action button
  • Updated subtitle to conditionally display only when page rules exist
  • Moved filter bar, notice, and table into else block for populated
    state
  • Enhanced empty state with "Block a page" heading and documentation
    link
+98/-71 
sites.ex
Enhanced sites list with dynamic empty state handling       

lib/plausible_web/live/sites.ex

  • Added logic to compute is_empty_state?, empty_state_title, and
    empty_state_description based on team setup and site availability
  • Added searching? assignment to track when filter text is active
  • Restructured UI to show centered empty state with heading,
    description, and action buttons
  • Updated search result message and conditionally show "Go to team
    sites" button based on context
+65/-21 
shared_link_settings.ex
Redesigned shared links empty state with tile wrapper       

lib/plausible_web/live/shared_link_settings.ex

  • Wrapped shared links section in .tile component with feature gating
  • Restructured to show centered empty state when no shared links exist
  • Moved form modal, filter bar, and table into conditional blocks
  • Updated empty state message from "No shared links configured" to
    "Create your first shared link"
+102/-68
country_rules.ex
Implemented centered empty state for country rules             

lib/plausible_web/live/shields/country_rules.ex

  • Added conditional rendering for empty state with centered layout and
    action button
  • Updated subtitle to conditionally display only when country rules
    exist
  • Moved filter bar, notice, and table into else block for populated
    state
  • Enhanced empty state with "Block a country" heading and documentation
    link
+95/-68 
goal_settings.ex
Refactored goal settings with tile wrapper and feature toggle

lib/plausible_web/live/goal_settings.ex

  • Removed site_role assignment logic and replaced with direct
    current_user usage
  • Wrapped goal settings in .tile component with feature toggle and
    gating
  • Added subtitle that displays when goals exist
  • Moved form and list components inside tile with proper feature
    handling
+57/-39 
funnel_settings.ex
Refactored funnel settings with tile wrapper and feature toggle

extra/lib/plausible_web/live/funnel_settings.ex

  • Wrapped funnel settings in .tile component with feature toggle and
    gating
  • Added subtitle that displays when funnels exist
  • Restructured empty state to show centered heading and description when
    goals are insufficient
  • Added handle_info callback to handle feature toggle messages
+58/-33 
list.ex
Implemented conditional filter bar and enhanced empty state for goals

lib/plausible_web/live/goal_settings/list.ex

  • Added searching? assignment to track active filter text
  • Conditionally show filter bar only when searching or goals exist
  • Extracted empty state and no search results into separate components
  • Updated empty state message to "Create your first goal" with
    documentation link
+56/-23 
list.ex
Implemented conditional filter bar and enhanced empty state for
properties

lib/plausible_web/live/props_settings/list.ex

  • Added searching? assignment to track active filter text
  • Conditionally show filter bar only when searching or properties exist
  • Extracted empty state and no search results into separate components
  • Updated empty state message to "Create a custom property" with
    documentation link
+47/-17 
list.ex
Implemented conditional filter bar and enhanced empty state for
funnels

extra/lib/plausible_web/live/funnel_settings/list.ex

  • Added searching? assignment to track active filter text
  • Conditionally show filter bar only when searching or funnels exist
  • Extracted empty state and no search results into separate components
  • Updated empty state message to "Create your first funnel" with
    documentation link
+45/-16 
form.ex
Refactored goal form to use current_user instead of site_role

lib/plausible_web/live/goal_settings/form.ex

  • Removed site_role attribute and replaced with current_user throughout
  • Updated component calls to pass current_user instead of site_role
  • Updated billing upgrade call to action to use current_user parameter
+5/-6     
generic.ex
Updated tile component with current_user and show_content attributes

lib/plausible_web/components/generic.ex

  • Changed current_role attribute to current_user in tile component
  • Added show_content? attribute to conditionally hide content sections
  • Updated feature gate to use current_user instead of current_role
  • Added new highlighted component for styling emphasized text
+24/-9   
toggle_live.ex
New feature toggle live component for dashboard visibility

lib/plausible_web/components/site/toggle_live.ex

  • New live component for rendering feature toggles in LiveView contexts
  • Handles feature toggle state management and messaging
  • Sends feature toggle events to parent component with success messages
  • Supports disabled state based on feature availability
+90/-0   
billing.ex
Refactored billing component to use current_user parameter

lib/plausible_web/components/billing/billing.ex

  • Changed current_role attribute to current_user in feature_gate
    component
  • Added site attribute to feature_gate for site-specific role resolution
  • Updated upgrade_call_to_action to derive role from user and team/site
    context
  • Enhanced role determination logic to handle both site and team roles
+19/-3   
props_settings.ex
Refactored props settings with tile wrapper and feature toggle

lib/plausible_web/live/props_settings.ex

  • Wrapped props settings in .tile component with feature toggle and
    gating
  • Added subtitle that displays when properties exist
  • Moved form and list components inside tile with proper feature
    handling
  • Added handle_info callback to handle feature toggle messages
+41/-20 
settings.ex
Implemented centered empty state for plugin tokens             

lib/plausible_web/live/plugins/api/settings.ex

  • Added conditional rendering for empty state with centered layout and
    CTA button
  • Moved filter bar and table into else block to only show when tokens
    exist
  • Updated empty state message to "Create your first plugin token"
  • Enhanced empty state with description and action button
+18/-3   
notice.ex
Updated billing notice component to use current_user parameter

lib/plausible_web/components/billing/notice.ex

  • Changed current_role attribute to current_user in limit_exceeded
    component
  • Updated component call to pass current_user to upgrade_call_to_action
+2/-2     
team_setup.ex
Updated team setup to use current_user parameter                 

lib/plausible_web/live/team_setup.ex

  • Changed current_role parameter to current_user in feature_gate
    component call
+1/-1     
team_management.ex
Updated team management to use current_user parameter       

lib/plausible_web/live/team_management.ex

  • Changed current_role parameter to current_user in limit_exceeded
    component call
+1/-1     
api_keys.html.heex
Implemented centered empty state for API keys                       

lib/plausible_web/templates/settings/api_keys.html.heex

  • Added conditional rendering for empty state with centered layout and
    CTA button
  • Moved filter bar and table into else block to only show when API keys
    exist
  • Updated subtitle to conditionally display only when API keys exist
  • Enhanced empty state with heading, description, and action button
+58/-47 
settings_visibility.html.heex
Refactored shared links template structure                             

lib/plausible_web/templates/site/settings_visibility.html.heex

  • Removed tile wrapper from shared link settings and moved live_render
    outside
  • Changed current_role to current_user in embed dashboard tile
  • Simplified shared links section to render live component directly
+6/-22   
settings_goals.html.heex
Moved goal settings tile wrapper to live component             

lib/plausible_web/templates/site/settings_goals.html.heex

  • Removed tile wrapper and feature toggle logic from template
  • Moved tile wrapper into GoalSettings live component
  • Simplified template to render live component directly
+5/-28   
Additional files
10 files
feature.ex +0/-44   
site_controller.ex +0/-36   
router.ex +0/-4     
team_general.html.heex +1/-1     
invite_member_form.html.heex +1/-1     
new.html.heex +1/-1     
settings_funnels.html.heex +2/-17   
settings_integrations.html.heex +1/-1     
settings_people.html.heex +1/-1     
settings_props.html.heex +2/-18   

@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Feature Toggle: New feature toggle interactions (e.g., tile feature toggles and ToggleLive) may constitute
critical configuration changes but there is no clear evidence in the diff that these
actions are logged with user, timestamp, and outcome.

Referred Code
  <div :if={@subtitle != []} class="text-sm mt-px text-gray-500 dark:text-gray-400 leading-5">
    {render_slot(@subtitle)}
  </div>

  <.live_component
    :if={@feature_toggle?}
    module={PlausibleWeb.Components.Site.Feature.ToggleLive}
    id={"feature-toggle-#{@site.id}-#{@feature_mod}"}
    site={@site}
    feature_mod={@feature_mod}
    current_user={@current_user}
  />
</header>

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing Errors: The newly added empty-state and action buttons initiate imports but there is no visible
handling for failure states or disabled states beyond basic booleans, and no contextual
error messaging/logging for failed operations is shown in the diff.

Referred Code
<%= if Enum.empty?(@site_imports) do %>
  <div class="flex flex-col items-center justify-center pt-5 pb-6 max-w-md mx-auto">
    <h3 class="text-center text-base font-medium text-gray-900 dark:text-gray-100 leading-7">
      Import your first data
    </h3>
    <p class="text-center text-sm mt-1 text-gray-500 dark:text-gray-400 leading-5 text-pretty">
      Import data from external sources. Up to {Plausible.Imported.max_complete_imports()} imports are allowed at a time.
    </p>
    <div class="flex gap-x-4 mt-4">
      <.button_link
        theme="secondary"
        href={Plausible.Google.API.import_authorize_url(@site.id)}
        disabled={@import_in_progress? or @at_maximum?}
        mt?={false}
      >
        Import from
        <img
          src="/images/icon/google_analytics_logo.svg"
          alt="Google Analytics import"
          class="h-6 w-12 -my-1"
        />


 ... (clipped 33 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Input Handling: The new search-related conditions (e.g., searching? based on filter_text) and rendering
paths expand UI behavior but the diff does not show input validation/sanitization of
filter_text or other parameters, relying on templates and helpers without explicit
safeguards here.

Referred Code
    :invitations_map,
    Enum.map(assigns.invitations, &{&1.invitation.invitation_id, &1}) |> Enum.into(%{})
  )
  |> assign(:searching?, String.trim(assigns.filter_text) != "")

~H"""
<.flash_messages flash={@flash} />
<div class="container pt-6">
  <PlausibleWeb.Live.Components.Visitors.gradient_defs />
  <.upgrade_nag_screen :if={
    @needs_to_upgrade == {:needs_to_upgrade, :no_active_trial_or_subscription}
  } />

  <div class="group mt-6 pb-5 border-b border-gray-200 dark:border-gray-750 flex items-center gap-2">
    <h2 class="text-xl font-bold leading-7 text-gray-900 dark:text-gray-100 sm:text-2xl md:text-3xl sm:leading-9 min-w-0 truncate">
      {Teams.name(@current_team)}
    </h2>
    <.unstyled_link
      :if={Teams.setup?(@current_team)}
      data-test-id="team-settings-link"
      href={Routes.settings_path(@socket, :team_general)}


 ... (clipped 47 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Extract duplicated empty state UI

The new empty state UI is duplicated across many files. This repeated code
should be extracted into a reusable component to improve maintainability and
consistency.

Examples:

lib/plausible_web/live/shields/ip_rules.ex [39-60]
          <%= if Enum.empty?(@ip_rules) do %>
            <div class="flex flex-col items-center justify-center pt-5 pb-6 max-w-md mx-auto">
              <h3 class="text-center text-base font-medium text-gray-900 dark:text-gray-100 leading-7">
                Block an IP address
              </h3>
              <p class="text-center text-sm mt-1 text-gray-500 dark:text-gray-400 leading-5 text-pretty">
                Reject incoming traffic from specific IP addresses.
                <.styled_link href="https://plausible.io/docs/excluding" target="_blank">
                  Learn more
                </.styled_link>

 ... (clipped 12 lines)
lib/plausible_web/live/imports_exports_settings.ex [84-115]
      <%= if Enum.empty?(@site_imports) do %>
        <div class="flex flex-col items-center justify-center pt-5 pb-6 max-w-md mx-auto">
          <h3 class="text-center text-base font-medium text-gray-900 dark:text-gray-100 leading-7">
            Import your first data
          </h3>
          <p class="text-center text-sm mt-1 text-gray-500 dark:text-gray-400 leading-5 text-pretty">
            Import data from external sources. Up to {Plausible.Imported.max_complete_imports()} imports are allowed at a time.
          </p>
          <div class="flex gap-x-4 mt-4">
            <.button_link

 ... (clipped 22 lines)

Solution Walkthrough:

Before:

# In multiple files like ip_rules.ex, hostname_rules.ex, etc.
<%= if Enum.empty?(@ip_rules) do %>
  <div class="flex flex-col items-center justify-center pt-5 pb-6 max-w-md mx-auto">
    <h3 class="text-center ...">
      Block an IP address
    </h3>
    <p class="text-center ...">
      Reject incoming traffic from specific IP addresses.
      <.styled_link href="...">Learn more</.styled_link>
    </p>
    <.button class="mt-4">
      Add IP address
    </.button>
  </div>
<% else %>
  ...
<% end %>

After:

# In a new component file, e.g., generic.ex
def empty_state(assigns) do
  ~H"""
  <div class="flex flex-col items-center justify-center pt-5 pb-6 max-w-md mx-auto">
    <h3 class="text-center ...">
      <%= render_slot(@title) %>
    </h3>
    <p class="text-center ...">
      <%= render_slot(@description) %>
    </p>
    <div class="flex gap-x-4 mt-4">
      <%= render_slot(@actions) %>
    </div>
  </div>
  """
end

# In files like ip_rules.ex, hostname_rules.ex, etc.
<.empty_state>
  <:title>Block an IP address</:title>
  <:description>...</:description>
  <:actions><.button>Add IP address</.button></:actions>
</.empty_state>

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies significant code duplication for the new empty state UI across many files, and abstracting it would improve maintainability.

Medium
General
Remove inconsistent feature-check wrapper

Remove the :if condition from the div wrapper around the live_render call to
align with the refactoring pattern of moving feature-check logic into the
LiveView component.

lib/plausible_web/templates/site/settings_visibility.html.heex [30-34]

-<div :if={Plausible.Billing.Feature.SharedLinks.enabled?(@site)}>
+<div>
   {live_render(@conn, PlausibleWeb.Live.SharedLinkSettings,
     session: %{"site_id" => @site.id, "domain" => @site.domain}
   )}
 </div>
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies an inconsistency in the refactoring pattern applied across the PR, improving code consistency and ensuring the component behaves as intended.

Medium
Simplify complex boolean logic expression

Simplify the complex boolean expression for the is_empty_state? variable to
improve readability and maintainability.

lib/plausible_web/live/sites.ex [62-64]

-is_empty_state? =
-  not (sites.entries != [] and (Teams.setup?(current_team) or has_sites?)) and
-    filter_text == ""
+is_empty_state? = sites.entries == [] and filter_text == ""
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that the boolean logic for is_empty_state? is overly complex and can be simplified, which improves code readability and maintainability.

Low
  • More

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants