Make a dismissible only alert component

Create a reusable DismissibleAlertComponent for consistent callout testing

Background & Context

This follow-up issue was created from a discussion in MR !191338, which implemented dismissible alerts for GitLab Duo with Amazon Q trial notifications. During the review, @dstull suggested creating a standardized DismissibleAlertComponent to reduce the testing burden for dismissible callouts across the application.

Currently, each dismissible alert implementation requires its own feature specs to test the dismissal logic, leading to repetitive test code and maintenance overhead.

Current State

The GitLab codebase currently uses multiple approaches for dismissible alerts:

  1. JavaScript-based alerts using createAlert() from app/assets/javascripts/alert.js
  2. Server-side alerts using Pajamas::AlertComponent with dismissible: true
  3. Custom dismissible components with their own dismissal logic

Each implementation requires individual feature specs to verify:

  • Alert visibility
  • Dismissal functionality
  • Persistence of dismissal state
  • Proper data attributes and endpoints

Examples of current dismissible alert usage:

  • ee/app/views/shared/_pipl_compliance_alert.html.haml
  • app/views/projects/_lfs_misconfiguration_banner.html.haml
  • ee/app/views/dashboard/projects/_joining_a_project_alert.haml

Requested Changes

Create a standardized DismissibleAlertComponent that:

  1. Encapsulates common dismissal logic - Handles the standard dismissal pattern used across GitLab
  2. Provides consistent testing interface - Includes well-tested dismissal behavior that doesn't require feature specs for each usage
  3. Maintains backward compatibility - Works alongside existing Pajamas::AlertComponent usage
  4. Supports standard dismissal patterns - Integrates with existing callout helpers and dismissal endpoints

Proposed Implementation

Click to expand

1. Create DismissibleAlertComponent

# app/components/dismissible_alert_component.rb
class DismissibleAlertComponent < Pajamas::AlertComponent
  def initialize(
    feature_id:,
    dismiss_endpoint: nil,
    user: nil,
    **alert_options
  )
    @feature_id = feature_id
    @dismiss_endpoint = dismiss_endpoint || default_dismiss_endpoint
    @user = user || current_user
    
    super(**alert_options.merge(
      dismissible: true,
      alert_options: build_alert_options(alert_options[:alert_options] || {})
    ))
  end

  private

  def build_alert_options(existing_options)
    existing_options.merge(
      class: "js-dismissible-alert #{existing_options[:class]}",
      data: {
        feature_id: @feature_id,
        dismiss_endpoint: @dismiss_endpoint,
        **existing_options.fetch(:data, {})
      }
    )
  end

  def default_dismiss_endpoint
    # Use Rails route helpers to generate appropriate endpoint
    # This would need to be context-aware (project vs group vs user callouts)
  end
end

2. Create comprehensive component specs

# spec/components/dismissible_alert_component_spec.rb
RSpec.describe DismissibleAlertComponent do
  # Test dismissal logic, data attributes, endpoint configuration
  # This comprehensive test suite eliminates need for feature specs
end

3. Create shared examples for integration testing

# spec/support/shared_examples/dismissible_alert_examples.rb
RSpec.shared_examples 'a dismissible alert' do |feature_id|
  it 'renders with correct dismissal attributes' do
    expect(page).to have_css("[data-feature-id='#{feature_id}']")
    expect(page).to have_css('.js-dismissible-alert')
  end

  it 'can be dismissed', :js do
    find('.js-close').click
    expect(page).not_to have_css("[data-feature-id='#{feature_id}']")
  end
end

4. Update existing dismissible alerts

Gradually migrate existing dismissible alerts to use the new component:

-# Before
= render Pajamas::AlertComponent.new(
  dismissible: true,
  alert_options: { 
    class: 'js-pipl-compliance-alert',
    data: { 
      feature_id: Users::CalloutsHelper::PIPL_COMPLIANCE_ALERT,
      dismiss_endpoint: callouts_path 
    }
  }
)

-# After  
= render DismissibleAlertComponent.new(
  feature_id: Users::CalloutsHelper::PIPL_COMPLIANCE_ALERT,
  dismiss_endpoint: callouts_path
)

Acceptance Criteria

  • DismissibleAlertComponent is created and inherits from Pajamas::AlertComponent
  • Component automatically handles standard dismissal data attributes and CSS classes
  • Component integrates with existing callout helpers (Users::CalloutsHelper, etc.)
  • Comprehensive component specs cover all dismissal scenarios
  • Shared examples are available for integration testing
  • Documentation is updated with usage examples
  • At least 3 existing dismissible alerts are migrated to use the new component
  • Feature specs for migrated alerts are simplified or removed
  • Component supports both user-level and project-level dismissals

Technical Context

Affected Files:

  • app/components/dismissible_alert_component.rb (new)
  • app/components/pajamas/alert_component.rb (reference)
  • app/assets/javascripts/alert.js (integration)
  • spec/components/dismissible_alert_component_spec.rb (new)
  • spec/support/shared_examples/dismissible_alert_examples.rb (new)

Integration Points:

  • Users::CalloutsHelper for user dismissals
  • Projects::CalloutsHelper for project-level dismissals
  • Existing dismissal endpoints (callouts_path, project_callouts_path)
  • JavaScript dismissal handlers

Dependencies:

  • Pajamas Design System components
  • Existing callout infrastructure
  • Rails view component framework

Priority: Medium - This is a developer experience improvement that will reduce maintenance overhead for future dismissible alerts.

Edited by Kamil Niechajewicz