Skip to content

DashboardSectionProvider plugins

How to contribute a section to /admin/reports/wow.

When to use this

  • Your submodule has status your operators should see (row counts, last-sync time, queue depth, something warning-worthy).
  • You want administrators to trigger one-click actions (sync, resync, wipe, reset state) from the dashboard.

The interface

Drupal\wow\DashboardSectionProviderInterface

Plugins return value objects, never HTML:

  • DashboardSection — the top-level block with a title and rows.
  • DashboardRow — one line inside a section: severity (OK / warning / error), label, value, optional actions.
  • DashboardAction — a dropdown item on a row: ID, label, optional destructive: TRUE flag to force a confirmation form.

DashboardRenderer owns all markup. A theme refresh to the dashboard doesn't ripple out to every plugin.

The attribute

use Drupal\wow\Attribute\DashboardSectionProvider;

#[DashboardSectionProvider(
  id: 'wow_my_thing',
  label: new TranslatableMarkup('My thing'),
)]
class MyThingStatus extends DashboardSectionProviderBase { /* ... */ }

Plugins live in modules/<module>/src/Plugin/DashboardSectionProvider/.

Two base classes

TaxonomyDashboardProviderBase

For taxonomy-backed reference-data modules. You implement five metadata getters and the base class handles:

  • term counts
  • last-sync timestamp (wow.last_modified.<entity>_index.<region>)
  • the standard "Sync now" action
  • skipping the wipe action (taxonomy wipes would break entity_reference integrity)

Reference: modules/wow_playable_class/src/Plugin/DashboardSectionProvider/PlayableClassStatus.php.

DashboardSectionProviderBase

The generic base. Use when your module has its own content entities, queue state, or non-standard actions to surface. Reference implementations:

  • modules/wow_achievement/src/Plugin/DashboardSectionProvider/AchievementStatus.php — queue depth, category counts, batch sync
  • modules/wow_character/src/Plugin/DashboardSectionProvider/CharacterStatus.php — stored count, force-resync, wipe
  • src/Plugin/DashboardSectionProvider/BattleNetStatus.php — API connection health, credential status

Implementing actions

Actions are IDs in your DashboardAction objects; when clicked, Drupal routes to your plugin's executeAction():

public function executeAction(string $actionId, array $parameters = []): ?BatchBuilder {
  return match ($actionId) {
    'sync_now'     => $this->buildSyncBatch($this->defaultRegion()),
    'force_resync' => $this->buildSyncBatch($this->defaultRegion(), resetState: TRUE),
    'wipe'         => $this->buildWipeBatch(),
    default        => parent::executeAction($actionId, $parameters),
  };
}

Return a BatchBuilder — the dashboard controller runs it as a Drupal batch with progress. Return NULL for actions that complete immediately (e.g. state reset).

Destructive actions (destructive: TRUE) route through DashboardActionConfirmForm before executing, so the operator sees a "Are you sure?" step.

Testing expectations

Kernel tests for dashboard plugins are straightforward — see PlayableClassStatusTest or AchievementStatusTest:

  1. Install the module + schemas.
  2. Seed some state (stored rows, a state key).
  3. Invoke the plugin's getSection() directly and assert severity, label, value, action presence.

Tests don't need to render markup — assert against the value objects.

Common pitfalls

  • Hardcoded region fallback: always fall back to 'eu' (matches config/install/wow.settings.yml). Don't use 'us' — it diverges from what other modules assume.
  • Writing HTML in the plugin: don't. Return value objects. If you need custom markup, work with the core maintainers on expanding DashboardRenderer — not in-plugin markup.
  • Coupling to another module's services: a dashboard plugin inside wow_mount should only use wow_mount's own services + core. Cross-module dependencies in dashboard plugins tend to indicate a deeper architecture problem.