Skip to content

Conversation

@tomerqodo
Copy link

@tomerqodo tomerqodo commented Dec 4, 2025

User description

Benchmark PR plausible#5874

Type: Corrupted (contains bugs)

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

  • Comprehensive redesign of empty states across settings pages with centered card layouts, improved messaging, and call-to-action buttons

  • Enhanced empty state UX for sites listing, shields (IP rules, hostname rules, page rules, country rules), imports/exports, shared links, goals, properties, funnels, and API keys

  • Refactored component parameter passing from current_role to current_user for better context awareness in billing and feature gating components

  • Added new feature toggle live component (toggle_live.ex) for dashboard toggle functionality

  • Implemented conditional subtitle visibility - subtitles now only display when content exists

  • Updated empty state messaging to be more action-oriented (e.g., "Create your first goal" instead of "No goals configured")

  • Added searching? logic to distinguish between empty states and no search results scenarios

  • Wrapped settings components in .tile containers with feature toggle and gating support

  • Removed deprecated site_role attribute and replaced with current_user parameter throughout

  • Updated all related test files to match new empty state messages and parameter structures

  • Simplified template structure by moving tile wrappers from templates into LiveView components


Diagram Walkthrough

flowchart LR
  A["Settings Pages<br/>Sites, Goals, Props, etc."] -->|"Redesigned with<br/>centered cards"| B["Enhanced Empty States"]
  B -->|"Action-oriented<br/>messaging"| C["Improved UX"]
  D["current_role Parameter"] -->|"Refactored to"| E["current_user Parameter"]
  E -->|"Better context<br/>for billing & gating"| F["Component Updates"]
  G["Conditional Subtitles"] -->|"Only show when<br/>content exists"| H["Cleaner UI"]
  I["New Feature Toggle<br/>Component"] -->|"Enables"| J["Dashboard Toggles"]
Loading

File Walkthrough

Relevant files
Tests
12 files
site_controller_test.exs
Enhanced empty state test coverage and messaging                 

test/plausible_web/controllers/site_controller_test.exs

  • Expanded test coverage for empty states with multiple scenarios
    (personal sites, team sites, search results)
  • Updated assertions to match new empty state messaging ("Add your first
    personal site", "Add your first team site")
  • Updated search result message from "Please search for something else"
    to "Try a different search term"
  • Updated shared links and imports empty state messages to match new UI
    pattern
+73/-122
props_settings_test.exs
Updated props settings tests for new empty states               

test/plausible_web/live/props_settings_test.exs

  • Updated assertions to match new empty state message "Create a custom
    property"
  • Added setup for properties in tests that require them for search input
    visibility
  • Added new test for dashboard toggle functionality
  • Removed commented-out test descriptions
+18/-13 
notice_test.exs
Updated billing notice tests for 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
  • Maintained test logic while adapting to new parameter structure
+20/-19 
goal_settings_test.exs
Updated goal settings tests for new messaging                       

test/plausible_web/live/goal_settings_test.exs

  • Updated assertions to match new empty state message "Create your first
    goal"
  • Added setup for goals in tests requiring search input visibility
  • Added new test for dashboard toggle functionality
+14/-3   
billing_test.exs
Updated billing component tests for 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 and team members
  • Maintained test assertions while adapting to new parameter structure
+15/-9   
funnel_settings_test.exs
Updated funnel settings tests                                                       

test/plausible_web/live/funnel_settings_test.exs

  • Updated test to use setup_funnels instead of setup_goals
  • Updated assertion text to match new empty state message "Set up a few
    goals"
  • Added new test for dashboard toggle functionality
+12/-2   
sites_test.exs
Updated sites test assertions for new messaging                   

test/plausible_web/live/sites_test.exs

  • Updated assertions to match new empty state messages
  • Changed from "You don't have any sites yet" to "Add your first
    personal site"
  • Changed from "Go to your team" to "Go to team sites"
+5/-5     
hostnames_test.exs
Updated hostname shields test assertions                                 

test/plausible_web/live/shields/hostnames_test.exs

  • Updated assertion to match new empty state message "Allow a hostname"
  • Updated assertion for informational text about hostname acceptance
+2/-2     
ip_addresses_test.exs
Updated IP addresses shields test assertion                           

test/plausible_web/live/shields/ip_addresses_test.exs

  • Updated assertion to match new empty state message "Block an IP
    address"
+1/-1     
countries_test.exs
Updated countries shields test assertion                                 

test/plausible_web/live/shields/countries_test.exs

  • Updated assertion to match new empty state message "Block a country"
+1/-1     
pages_test.exs
Updated pages shields test assertion                                         

test/plausible_web/live/shields/pages_test.exs

  • Updated assertion to match new empty state message "Block a page"
+1/-1     
shared_link_settings_test.exs
Updated shared link settings test assertion                           

test/plausible_web/live/shared_link_settings_test.exs

  • Updated assertion to match new empty state message "Create your first
    shared link"
+1/-1     
Enhancement
29 files
imports_exports_settings.ex
Redesigned imports/exports empty state UI                               

lib/plausible_web/live/imports_exports_settings.ex

  • Wrapped empty state in centered card layout with title and description
  • Moved import buttons into empty state card when no imports exist
  • Conditional rendering of table and buttons based on import list
    presence
  • Updated empty state message from "There are no imports yet" to "Import
    your first data"
+127/-90
ip_rules.ex
Refactored IP rules empty state presentation                         

lib/plausible_web/live/shields/ip_rules.ex

  • Implemented conditional empty state with centered card layout
  • Moved subtitle to only show when rules exist
  • Wrapped table and filter bar in conditional block
  • Updated empty state message from "No IP rules configured" to "Block an
    IP address"
+105/-81
hostname_rules.ex
Redesigned hostname rules empty state layout                         

lib/plausible_web/live/shields/hostname_rules.ex

  • Added conditional empty state with centered card design
  • Made subtitle conditional to only display when rules exist
  • Wrapped table and controls in conditional block
  • Updated empty state messaging and added documentation link
+100/-76
page_rules.ex
Updated page rules empty state design                                       

lib/plausible_web/live/shields/page_rules.ex

  • Implemented centered empty state card with title and description
  • Made subtitle conditional based on rule existence
  • Wrapped table and filter controls in conditional block
  • Updated empty state message from "No page rules configured" to "Block
    a page"
+98/-71 
sites.ex
Implemented enhanced empty state for sites listing             

lib/plausible_web/live/sites.ex

  • Added logic to determine empty state display based on sites and team
    setup
  • Introduced is_empty_state?, empty_state_title, and
    empty_state_description assigns
  • Replaced simple text messages with centered card layout containing
    title, description, and CTAs
  • Added "Go to team sites" button for users with personal sites but
    viewing team context
+65/-21 
shared_link_settings.ex
Refactored shared links settings with tile wrapper             

lib/plausible_web/live/shared_link_settings.ex

  • Wrapped entire component in .tile component with feature gating
  • Implemented conditional empty state with centered card layout
  • Moved form modal and buttons into conditional blocks
  • Updated empty state message from "No shared links configured" to
    "Create your first shared link"
+102/-68
country_rules.ex
Enhanced country rules empty state presentation                   

lib/plausible_web/live/shields/country_rules.ex

  • Added conditional empty state with centered card design
  • Made subtitle conditional to only show when rules exist
  • Wrapped table and controls in conditional block
  • Updated documentation link to include country-specific anchor
+95/-68 
goal_settings.ex
Refactored goal settings with tile wrapper                             

lib/plausible_web/live/goal_settings.ex

  • Removed site_role assignment logic
  • Wrapped component in .tile with feature toggle and gating
  • Added conditional subtitle display based on goal count
  • Moved form component inside tile structure
+57/-39 
funnel_settings.ex
Enhanced funnel settings with tile wrapper                             

extra/lib/plausible_web/live/funnel_settings.ex

  • Wrapped component in .tile with feature toggle and gating
  • Added conditional subtitle display based on funnel count
  • Updated empty state styling to match new centered card pattern
  • Added feature toggle handler for flash messages
+58/-33 
list.ex
Refactored goal list with improved empty states                   

lib/plausible_web/live/goal_settings/list.ex

  • Added searching? assign based on filter text
  • Implemented conditional rendering of filter bar and search results
  • Created separate empty_state and no_search_results helper functions
  • Updated empty state message from "No goals configured" to "Create your
    first goal"
+56/-23 
list.ex
Refactored props list with enhanced empty states                 

lib/plausible_web/live/props_settings/list.ex

  • Added searching? assign based on filter text
  • Implemented conditional filter bar display
  • Created separate empty_state and no_search_results helper functions
  • Updated empty state message from "No properties configured" to "Create
    a custom property"
+47/-17 
list.ex
Enhanced funnel list with improved empty states                   

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

  • Added searching? assign based on filter text
  • Implemented conditional filter bar display
  • Created separate empty_state and no_search_results helper functions
  • Updated empty state message from "No funnels configured" to "Create
    your first funnel"
+45/-16 
form.ex
Updated goal form to use current_user parameter                   

lib/plausible_web/live/goal_settings/form.ex

  • Removed site_role attribute and replaced with current_user
  • Updated component calls to pass current_user instead of site_role
  • Updated billing upgrade call to use current_user parameter
+5/-6     
generic.ex
Updated generic components for user-based parameters         

lib/plausible_web/components/generic.ex

  • Replaced current_role attribute with current_user
  • Added show_content? boolean attribute for conditional content display
  • Updated feature gate component call with new parameters
  • Added highlighted helper component for styled text emphasis
+24/-9   
toggle_live.ex
Added new feature toggle live component                                   

lib/plausible_web/components/site/toggle_live.ex

  • Created new LiveComponent for feature toggle functionality
  • Implements toggle button with visual state indicators
  • Sends feature toggle events to parent component
  • Handles disabled state based on feature availability
+90/-0   
billing.ex
Refactored billing component for user-based parameters     

lib/plausible_web/components/billing/billing.ex

  • Replaced current_role attribute with current_user
  • Added site attribute for site-specific role determination
  • Updated upgrade_call_to_action to derive role from user and team/site
    context
  • Maintained backward compatibility with role determination logic
+21/-3   
props_settings.ex
Enhanced props settings with tile wrapper                               

lib/plausible_web/live/props_settings.ex

  • Wrapped component in .tile with feature toggle and gating
  • Added conditional subtitle display based on property count
  • Added feature toggle handler for flash messages
+41/-20 
settings.ex
Redesigned plugin tokens empty state UI                                   

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

  • Implemented conditional empty state with centered card layout
  • Moved filter bar and table into conditional block
  • Updated empty state message from implicit to "Create your first plugin
    token"
+18/-3   
notice.ex
Updated notice component for user parameter                           

lib/plausible_web/components/billing/notice.ex

  • Changed current_role attribute to current_user
  • Updated component call to pass current_user to upgrade call to action
+2/-2     
team_setup.ex
Updated team setup for user parameter                                       

lib/plausible_web/live/team_setup.ex

  • Changed feature gate parameter from current_role to current_user
+1/-1     
team_management.ex
Updated team management for user parameter                             

lib/plausible_web/live/team_management.ex

  • Changed notice parameter from current_role to current_user
+1/-1     
api_keys.html.heex
Redesigned API keys empty state UI                                             

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

  • Implemented conditional empty state with centered card layout
  • Moved filter bar and table into conditional block
  • Updated empty state message from "No API keys configured yet" to
    "Create your first API key"
+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
  • Moved shared link settings rendering directly into template
  • Updated embed dashboard tile to use current_user instead of
    current_role
+6/-22   
settings_goals.html.heex
Simplified goals settings template                                             

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

  • Removed tile wrapper from goals settings
  • Moved goals settings rendering directly into template
+5/-28   
settings_imports_exports.html.heex
Simplified imports/exports template structure                       

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

  • Removed tile wrapper from imports/exports settings
  • Moved imports/exports rendering directly into template
  • Kept export tile with updated documentation link
+5/-16   
settings_props.html.heex
Simplified props settings template                                             

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

  • Removed tile wrapper from props settings
  • Moved props settings rendering directly into template
+2/-18   
invite_member_form.html.heex
Updated invite member form for user parameter                       

lib/plausible_web/templates/site/membership/invite_member_form.html.heex

  • Changed notice parameter from current_role to current_user
+1/-1     
settings_funnels.html.heex
Simplified funnels settings template                                         

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

  • Removed tile wrapper from funnels settings
  • Moved funnels settings rendering directly into template
+2/-17   
settings_integrations.html.heex
Updated integrations template subtitle visibility               

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

  • Made subtitle conditional to only display when plugin tokens exist
+1/-1     
Additional files
6 files
feature.ex +0/-44   
site_controller.ex +0/-36   
router.ex +0/-4     
team_general.html.heex +1/-1     
new.html.heex +1/-1     
settings_people.html.heex +1/-1     

sanne-san and others added 10 commits November 13, 2025 04:18
- Add show_content? attribute to generic tile component
- Ensure content is hidden when toggled off
- Avoid rendering border and empty space when toggled off
- Fix formatting
- Update the settings live views to use the new tile component
- Ensure tile component is updated when feature visibility is toggled
- Extract `no_search_results` and `empty_state` components for better readability
- Extract `highlighted` component
- Update tests
- Hide top bar on `/sites` when empty state is shown
- Extract empty state logic to a separate function
- Show the same empty state for both personal and team sites, with different copy
- extract search logic to a separate function
- add tests for various empty states cases
  - remove HTTP feature visibility routes now that
    we're doing it 100% via LV
  - add tests for feature toggling
  - move "site_role" to where it's used (upgrade CTA),
    since there were already some feature-related function calls
    there
  - fix random test failures left
@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:
Missing Audit Log: New user-triggered actions like creating and deleting shared links are rendered via UI
components without evident auditing or logging in the diff.

Referred Code
<%= if Enum.empty?(@shared_links) 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">
      Create your first shared link
    </h3>
    <p class="text-center text-sm mt-1 text-gray-500 dark:text-gray-400 leading-5 text-pretty">
      Share your stats privately with anyone. Links are unique, secure, and can be password-protected.
      <.styled_link href="https://plausible.io/docs/shared-links" target="_blank">
        Learn more
      </.styled_link>
    </p>
    <.button
      id="add-shared-link-button"
      phx-click="add-shared-link"
      x-data
      x-on:click={Modal.JS.preopen("shared-links-form-modal")}
      class="mt-4"
    >
      Add shared link
    </.button>
  </div>


 ... (clipped 48 lines)

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:
Edge Cases Missing: The new empty state and action buttons do not show validation or error handling for states
like disabled buttons being bypassed or failures when invoking external import
authorization URLs.

Referred Code
  <.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"
    />
  </.button_link>
  <.button_link
    disabled={@import_in_progress? or @at_maximum?}
    href={"/#{URI.encode_www_form(@site.domain)}/settings/import"}
    mt?={false}
  >
    Import from CSV
  </.button_link>
</div>


 ... (clipped 24 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 Validation: UI enables adding hostnames/IPs/pages/countries with no visible client/server-side
validation or sanitization changes in this PR to prevent malformed or dangerous input.

Referred Code
    <.button
      :if={@hostname_rules_count < Shields.maximum_hostname_rules()}
      id="add-hostname-rule"
      x-data
      x-on:click={Modal.JS.open("hostname-rule-form-modal")}
      class="mt-4"
    >
      Add hostname
    </.button>
  </div>
<% else %>
  <.filter_bar
    :if={@hostname_rules_count < Shields.maximum_hostname_rules()}
    filtering_enabled?={false}
  >
    <.button
      id="add-hostname-rule"
      x-data
      x-on:click={Modal.JS.open("hostname-rule-form-modal")}
      mt?={false}
    >


 ... (clipped 3 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
Possible issue
Fix incorrect empty state logic

Fix the logic for is_empty_state? to correctly handle all empty state scenarios.
The current implementation is too restrictive and fails for cases like a user
having team sites but no personal sites.

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

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

__

Why: The suggestion correctly identifies a logic bug in is_empty_state? that causes it to fail in several empty state scenarios, such as when a user has team sites but no personal sites. Applying the fix makes the empty state logic robust and correct for all intended cases.

High
Add missing feature flag check

Re-add the :if={Plausible.Billing.Feature.Goals.enabled?(@site)} check to the
div that wraps the GoalSettings live view to ensure it is only rendered when the
feature is enabled.

lib/plausible_web/templates/site/settings_goals.html.heex [2-6]

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

__

Why: The suggestion correctly identifies that a feature flag check was removed during refactoring, which could expose a feature to users who are not entitled to it, representing a significant bug.

High
High-level
Avoid duplicating UI logic

Refactor the new empty state UI to eliminate code duplication. The current
if/else implementation repeats action buttons and markup across several
components.

Examples:

lib/plausible_web/live/imports_exports_settings.ex [84-203]
      <%= 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 110 lines)
lib/plausible_web/live/shields/ip_rules.ex [39-140]
          <%= 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 92 lines)

Solution Walkthrough:

Before:

# In lib/plausible_web/live/shields/ip_rules.ex
<%= if Enum.empty?(@ip_rules) do %>
  <div class="... mx-auto">
    <h3>Block an IP address</h3>
    <p>Reject incoming traffic...</p>
    <.button class="mt-4">
      Add IP address
    </.button>
  </div>
<% else %>
  <.filter_bar>
    <.button mt?={false}>
      Add IP address
    </.button>
  </.filter_bar>
  <.table rows={@ip_rules}>...</.table>
<% end %>

After:

# In lib/plausible_web/live/shields/ip_rules.ex
<%= if not Enum.empty?(@ip_rules) do %>
  <.filter_bar>
    <.button mt?={false}>
      Add IP address
    </.button>
  </.filter_bar>
  <.table rows={@ip_rules}>...</.table>
<% else %>
  <div class="... mx-auto">
    <h3>Block an IP address</h3>
    <p>Reject incoming traffic...</p>
    <.button class="mt-4">
      Add IP address
    </.button>
  </div>
<% end %>
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies significant code duplication in the new empty state UI across multiple files, which impacts maintainability.

Medium
  • 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.

4 participants