Skip to main content

Drupal Views AI Integration Guide

Drupal Views AI Integration Guide

Purpose: Comprehensive guide for using Drupal Views to display AI agent data, create custom views plugins, and export agent metrics in multiple formats.

Audience: Drupal developers building AI agent dashboards and monitoring systems.

Related Documentation:


Table of Contents

  1. Overview
  2. Views API Fundamentals
  3. AI Data Display Patterns
  4. Custom Views Plugins
  5. Data Export Formats
  6. Real-Time Views Updates
  7. Code Examples
  8. Best Practices
  9. Troubleshooting

Overview

What are Views?

Drupal Views is a powerful query builder that allows developers and administrators to display entity data without writing SQL. Views can display:

  • Entity lists (nodes, users, custom entities)
  • Relationships between entities
  • Aggregated data
  • Grouped results
  • Custom formatted output

Why Views for AI Agent Data?

Views are ideal for AI agent monitoring because they:

  • Declarative Configuration: UI-based view configuration stored in YAML
  • Flexible Filtering: Filter agent executions by status, timestamp, cost
  • Real-Time Data: Display current agent metrics from custom data sources
  • Multiple Displays: Show same data in different formats (page, block, REST API, export)
  • Exportable: Built-in support for CSV, JSON, XML, XLS formats
  • Cacheable: Views support caching for performance
  • Extensible: Custom plugins for handlers, filters, fields, displays

Agent Use Cases

Agent Execution History        Views listing all agent runs with filters
Agent Performance Metrics      Views displaying aggregated agent statistics
Token Cost Analysis           Views grouping executions by cost ranges
Agent Error Tracking          Views filtered by failure status and error types
Team Agent Dashboard          Views showing agents assigned to specific teams
Real-Time Agent Status        WebSocket-based views updates for live monitoring
Audit Logs                    Views for AI decision logging and compliance

Views API Fundamentals

View Architecture

View
 Display 1 (page, block, REST API, export)
    Handler 1 (relationship)
    Handler 2 (filter)
    Handler 3 (field)
    Handler 4 (sort)
 Display 2
 Display 3

Plugin Types

1. [object Object]

How the view is rendered (page, block, REST, export).

# Example: Custom display plugin type: custom_display class: 'Drupal\my_module\Plugin\views\display\CustomDisplay'

2. [object Object]

Processing logic for view data (fields, filters, sorts, relationships).

Handler Types:

  • Field: Display entity data
  • Filter: Restrict rows (WHERE clause)
  • Sort: Order results
  • Relationship: Join between entities
  • Argument: Dynamic filtering from URL
# Example: Custom field handler type: field class: 'Drupal\ai_agents\Plugin\views\field\AgentTokenCost'

3. [object Object]

How data is formatted in output (table, grid, list, JSON).

# Example: AI metrics style plugin type: style class: 'Drupal\ai_agents_charts\Plugin\views\style\AIMetrics'

4. [object Object]

How individual rows are rendered.

# Example: Agent execution row format type: row class: 'Drupal\ai_agents\Plugin\views\row\AgentExecution'

5. [object Object]

Validate URL arguments (e.g., ensure agent_id exists).

# Example: Agent ID validator type: argument_validator class: 'Drupal\ai_agents\Plugin\views\argument_validator\AgentExists'

AI Data Display Patterns

Pattern 1: Direct Entity Views

Display agent entities directly using Views.

Structure:

display_plugins: page: type: page title: Agent Executions path: admin/agents/executions handlers: fields: title: { plugin: field } execution_id: { plugin: field } started_at: { plugin: date } finished_at: { plugin: date } status: { plugin: field } filters: status: value: [ running, completed, failed ] operator: in created: operator: '>=' value: '-1 week' sorts: created: DESC

Pattern 2: Custom Data Source Views

Display AI data from custom sources (API responses, external databases).

Requirements:

  1. Implement ViewsDataInterface to expose custom data
  2. Create custom field handlers
  3. Support filtering and sorting

Example Implementation:

<?php namespace Drupal\ai_agents\Plugin\views\field; use Drupal\views\Plugin\views\field\FieldPluginBase; /** * Views field plugin for agent execution metrics. * * @ViewsField("agent_execution_metrics") */ class AgentExecutionMetrics extends FieldPluginBase { /** * {@inheritdoc} */ public function render(ResultRow $values) { $execution_id = $values->id; $metrics = $this->getAgentMetrics($execution_id); return [ '#theme' => 'agent_metrics', '#metrics' => $metrics, ]; } protected function getAgentMetrics($execution_id) { // Fetch from external API, database, or cache return [ 'tokens_used' => 1500, 'cost' => 0.045, 'latency_ms' => 2340, 'api_calls' => 5, ]; } }

Pattern 3: Aggregated Data Views

Show summarized agent metrics (total tokens, average latency, error rates).

Using SQL aggregation:

SELECT agent_name, COUNT(*) as total_executions, AVG(tokens_used) as avg_tokens, SUM(cost) as total_cost, AVG(latency_ms) as avg_latency, COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed_count FROM ai_agent_executions GROUP BY agent_name

Implemented via custom field handler:

<?php namespace Drupal\ai_agents_analytics\Plugin\views\field; use Drupal\views\Plugin\views\field\FieldPluginBase; use Drupal\views\ResultRow; /** * Custom field for aggregate agent metrics. * * @ViewsField("agent_aggregate_metrics") */ class AgentAggregateMetrics extends FieldPluginBase { public function render(ResultRow $values) { $agent_id = $values->agent_id; $aggregates = \Drupal::database() ->select('ai_agent_executions', 'ae') ->fields('ae', []) ->condition('ae.agent_id', $agent_id) ->condition('ae.created', strtotime('-30 days'), '>') ->aggregate('ae.tokens_used', 'SUM', 'total_tokens') ->aggregate('ae.tokens_used', 'AVG', 'avg_tokens') ->aggregate('ae.cost', 'SUM', 'total_cost') ->aggregate('ae.latency_ms', 'AVG', 'avg_latency') ->execute() ->fetchAssoc(); return [ '#theme' => 'agent_metrics_summary', '#metrics' => $aggregates, ]; } }

Pattern 4: Real-Time Metrics Views

Display live agent data using custom field handlers that fetch current state.

<?php namespace Drupal\ai_agents\Plugin\views\field; use Drupal\views\Plugin\views\field\FieldPluginBase; use Drupal\views\ResultRow; /** * Real-time agent status field. * * @ViewsField("agent_realtime_status") */ class AgentRealTimeStatus extends FieldPluginBase { public function render(ResultRow $values) { $agent_id = $values->agent_id; // Fetch from cache or live API $status = $this->getAgentStatus($agent_id); return [ '#type' => 'html_tag', '#tag' => 'span', '#attributes' => [ 'class' => ['agent-status', 'status-' . $status], 'data-agent-id' => $agent_id, 'data-update-interval' => '5000', // 5 seconds ], '#value' => ucfirst($status), ]; } protected function getAgentStatus($agent_id) { $cache = \Drupal::cache(); $cache_key = "agent_status:{$agent_id}"; $cached = $cache->get($cache_key); if ($cached) { return $cached->data; } // Fetch from API or service $agent_service = \Drupal::service('ai_agents.service'); $status = $agent_service->getStatus($agent_id); // Cache for 30 seconds $cache->set($cache_key, $status, time() + 30); return $status; } }

Custom Views Plugins

Creating a Custom Field Plugin

File Structure:

my_module/
 src/
    Plugin/
        views/
            field/
                CustomAgentField.php
 my_module.views.inc

Step 1: Define Views Data (my_module.views.inc)

<?php /** * @file * Implements hook_views_data() for agent execution data. */ function my_module_views_data() { $data = []; // Base table $data['ai_agent_executions'] = [ 'table' => [ 'group' => t('Agent Executions'), 'base' => [ 'field' => 'id', 'title' => t('Agent Executions'), 'help' => t('AI agent execution records'), 'access query tag' => 'ai_agent_executions_access', ], ], ]; // Fields $data['ai_agent_executions']['id'] = [ 'title' => t('Execution ID'), 'help' => t('Unique execution identifier'), 'field' => [ 'id' => 'numeric', ], 'filter' => [ 'id' => 'numeric', ], 'sort' => [ 'id' => 'standard', ], 'argument' => [ 'id' => 'numeric', ], ]; $data['ai_agent_executions']['agent_id'] = [ 'title' => t('Agent ID'), 'help' => t('Agent identifier'), 'field' => [ 'id' => 'numeric', ], 'filter' => [ 'id' => 'numeric', ], 'argument' => [ 'id' => 'numeric', ], ]; $data['ai_agent_executions']['tokens_used'] = [ 'title' => t('Tokens Used'), 'help' => t('Total tokens consumed in execution'), 'field' => [ 'id' => 'numeric', ], 'filter' => [ 'id' => 'numeric', ], 'sort' => [ 'id' => 'standard', ], ]; $data['ai_agent_executions']['cost'] = [ 'title' => t('Cost'), 'help' => t('Execution cost in USD'), 'field' => [ 'id' => 'numeric', ], 'filter' => [ 'id' => 'numeric', ], ]; $data['ai_agent_executions']['status'] = [ 'title' => t('Status'), 'help' => t('Execution status'), 'field' => [ 'id' => 'standard', ], 'filter' => [ 'id' => 'string', 'operator' => 'in', 'options' => [ 'pending' => t('Pending'), 'running' => t('Running'), 'completed' => t('Completed'), 'failed' => t('Failed'), ], ], 'argument' => [ 'id' => 'string', ], ]; $data['ai_agent_executions']['created'] = [ 'title' => t('Created'), 'help' => t('Execution timestamp'), 'field' => [ 'id' => 'date', ], 'filter' => [ 'id' => 'date', ], 'sort' => [ 'id' => 'date', ], ]; return $data; }

Step 2: Create Field Plugin (CustomAgentField.php)

<?php namespace Drupal\my_module\Plugin\views\field; use Drupal\views\Plugin\views\field\FieldPluginBase; use Drupal\views\ResultRow; /** * Custom field plugin for agent execution metadata. * * @ViewsField("agent_execution_metadata") */ class AgentExecutionMetadata extends FieldPluginBase { /** * {@inheritdoc} */ protected function defineOptions() { $options = parent::defineOptions(); $options['display_format'] = ['default' => 'summary']; $options['show_details'] = ['default' => TRUE]; return $options; } /** * {@inheritdoc} */ public function buildOptionsForm(&$form, FormStateInterface $form_state) { parent::buildOptionsForm($form, $form_state); $form['display_format'] = [ '#type' => 'select', '#title' => $this->t('Display Format'), '#options' => [ 'summary' => $this->t('Summary Card'), 'detailed' => $this->t('Detailed Table'), 'timeline' => $this->t('Timeline'), ], '#default_value' => $this->options['display_format'], ]; $form['show_details'] = [ '#type' => 'checkbox', '#title' => $this->t('Show Detailed Metrics'), '#default_value' => $this->options['show_details'], ]; } /** * {@inheritdoc} */ public function render(ResultRow $values) { $execution_id = $values->id; $agent_id = $values->agent_id; // Fetch execution data $execution = $this->loadExecution($execution_id); // Build render array based on selected format switch ($this->options['display_format']) { case 'detailed': return $this->renderDetailed($execution); case 'timeline': return $this->renderTimeline($execution); case 'summary': default: return $this->renderSummary($execution); } } protected function renderSummary($execution) { return [ '#theme' => 'agent_execution_summary', '#execution' => $execution, '#attached' => [ 'library' => ['my_module/agent-metrics'], ], ]; } protected function renderDetailed($execution) { return [ '#theme' => 'agent_execution_detailed', '#execution' => $execution, '#metrics' => $this->options['show_details'], ]; } protected function renderTimeline($execution) { return [ '#theme' => 'agent_execution_timeline', '#execution' => $execution, ]; } protected function loadExecution($execution_id) { // Load from database or API $result = \Drupal::database() ->select('ai_agent_executions', 'ae') ->fields('ae') ->condition('ae.id', $execution_id) ->execute() ->fetchAssoc(); return $result; } }

Creating a Custom Filter Plugin

<?php namespace Drupal\my_module\Plugin\views\filter; use Drupal\views\Plugin\views\filter\FilterPluginBase; /** * Filter agent executions by cost range. * * @ViewsFilter("agent_cost_range") */ class AgentCostRange extends FilterPluginBase { /** * {@inheritdoc} */ protected function defineOptions() { $options = parent::defineOptions(); $options['operator'] = ['default' => 'between']; $options['value'] = ['default' => ['min' => 0, 'max' => 100]]; return $options; } /** * {@inheritdoc} */ public function buildOptionsForm(&$form, FormStateInterface $form_state) { parent::buildOptionsForm($form, $form_state); $form['operator'] = [ '#type' => 'select', '#title' => $this->t('Operator'), '#options' => [ 'between' => $this->t('Between'), 'less' => $this->t('Less Than'), 'greater' => $this->t('Greater Than'), ], '#default_value' => $this->options['operator'], ]; $form['value'] = [ '#type' => 'fieldset', '#title' => $this->t('Cost Range (USD)'), '#tree' => TRUE, ]; $form['value']['min'] = [ '#type' => 'number', '#title' => $this->t('Minimum'), '#step' => 0.01, '#default_value' => $this->options['value']['min'], ]; $form['value']['max'] = [ '#type' => 'number', '#title' => $this->t('Maximum'), '#step' => 0.01, '#default_value' => $this->options['value']['max'], ]; } /** * {@inheritdoc} */ public function query() { $this->ensureMyTable(); switch ($this->options['operator']) { case 'between': $this->query->addWhere( $this->options['group'], "$this->tableAlias.cost", [ $this->options['value']['min'], $this->options['value']['max'], ], 'BETWEEN' ); break; case 'less': $this->query->addWhere( $this->options['group'], "$this->tableAlias.cost", $this->options['value']['min'], '<' ); break; case 'greater': $this->query->addWhere( $this->options['group'], "$this->tableAlias.cost", $this->options['value']['min'], '>' ); break; } } }

Creating a Custom Display Plugin

<?php namespace Drupal\my_module\Plugin\views\display; use Drupal\views\Plugin\views\display\DisplayPluginBase; use Drupal\Core\Form\FormStateInterface; /** * Custom display for real-time agent metrics. * * @ViewsDisplay( * id = "agent_realtime_metrics", * title = @Translation("Agent Real-Time Metrics"), * help = @Translation("Display real-time agent metrics with WebSocket updates"), * uses_menu_links = FALSE, * uses_hook_menu = FALSE, * use_ajax = TRUE, * use_pager = FALSE * ) */ class AgentRealtimeMetrics extends DisplayPluginBase { /** * {@inheritdoc} */ protected function defineOptions() { $options = parent::defineOptions(); $options['update_interval'] = ['default' => 5000]; $options['websocket_enabled'] = ['default' => TRUE]; $options['metrics_to_display'] = ['default' => []]; return $options; } /** * {@inheritdoc} */ public function buildOptionsForm(&$form, FormStateInterface $form_state) { parent::buildOptionsForm($form, $form_state); $form['update_interval'] = [ '#type' => 'number', '#title' => $this->t('Update Interval (ms)'), '#description' => $this->t('How often to refresh metrics'), '#default_value' => $this->options['update_interval'], '#step' => 100, '#min' => 1000, ]; $form['websocket_enabled'] = [ '#type' => 'checkbox', '#title' => $this->t('Enable WebSocket Updates'), '#description' => $this->t('Use WebSocket for real-time push updates'), '#default_value' => $this->options['websocket_enabled'], ]; $form['metrics_to_display'] = [ '#type' => 'checkboxes', '#title' => $this->t('Metrics to Display'), '#options' => [ 'tokens' => $this->t('Token Usage'), 'cost' => $this->t('Cost'), 'latency' => $this->t('Latency'), 'errors' => $this->t('Error Count'), ], '#default_value' => $this->options['metrics_to_display'], ]; } /** * {@inheritdoc} */ public function render() { $output = parent::render(); $output['#attached']['library'][] = 'my_module/realtime-metrics'; $output['#attached']['drupalSettings']['agentMetrics'] = [ 'updateInterval' => $this->options['update_interval'], 'websocketEnabled' => $this->options['websocket_enabled'], 'metricsToDisplay' => array_filter($this->options['metrics_to_display']), ]; return $output; } }

Data Export Formats

Supported Export Formats

Drupal supports multiple export formats via the views_data_export module:

1. [object Object]

Best for: Spreadsheet analysis, data import, external processing

Configuration:

display_plugins: export_csv: type: views_data_export title: Export as CSV path: agents/export/csv style: id: views_data_export_csv options: provide_file: true filename: 'agent-executions-[date].csv' quote_character: '"' escape_character: '\'

Output Example:

Agent ID,Name,Status,Tokens Used,Cost,Created 001,CodeAnalyzer,completed,1500,0.045,2025-01-08T10:30:00 002,DocWriter,running,2300,0.069,2025-01-08T10:25:00 003,TestGenerator,failed,800,0.024,2025-01-08T10:20:00

2. [object Object]

Best for: API consumption, JavaScript processing, nested data

Configuration:

display_plugins: export_json: type: views_data_export title: Export as JSON path: agents/export/json style: id: views_data_export_json options: provide_file: true filename: 'agent-executions-[date].json' uses_row_index: false output_pretty_print: true

Output Example:

{ "executions": [ { "id": "001", "agent_id": "agent-001", "name": "CodeAnalyzer", "status": "completed", "tokens_used": 1500, "cost": 0.045, "created": "2025-01-08T10:30:00Z", "metadata": { "duration_ms": 2340, "api_calls": 5 } }, { "id": "002", "agent_id": "agent-002", "name": "DocWriter", "status": "running", "tokens_used": 2300, "cost": 0.069, "created": "2025-01-08T10:25:00Z", "metadata": { "duration_ms": 5000, "api_calls": 8 } } ] }

3. [object Object]

Best for: Enterprise integration, SOAP services, legacy systems

Configuration:

display_plugins: export_xml: type: views_data_export title: Export as XML path: agents/export/xml style: id: views_data_export_xml options: provide_file: true filename: 'agent-executions-[date].xml' root_element: 'executions' item_element: 'execution'

Output Example:

<?xml version="1.0" encoding="UTF-8"?> <executions> <execution> <id>001</id> <agent_id>agent-001</agent_id> <name>CodeAnalyzer</name> <status>completed</status> <tokens_used>1500</tokens_used> <cost>0.045</cost> <created>2025-01-08T10:30:00Z</created> <metadata> <duration_ms>2340</duration_ms> <api_calls>5</api_calls> </metadata> </execution> </executions>

4. [object Object]

Best for: End-user analysis, formatted reports, spreadsheet features

Requires: views_data_export_xls module

Configuration:

display_plugins: export_xls: type: views_data_export title: Export as Excel path: agents/export/xls style: id: views_data_export_xls options: provide_file: true filename: 'agent-executions-[date].xls' auto_width: true worksheet_name: 'Agent Executions'

Custom Export Format Plugin

Create a custom export format for specialized AI metrics:

<?php namespace Drupal\my_module\Plugin\views\style; use Drupal\views_data_export\Plugin\views\style\DataExport; use Drupal\views\ViewExecutable; /** * Exports AI agent metrics in custom JSON-LD format. * * @ViewsStyle( * id = "agent_metrics_export", * title = @Translation("Agent Metrics (JSON-LD)"), * help = @Translation("Export agent execution data in JSON-LD format"), * style_type = "table", * uses_row_plugin = FALSE, * uses_fields = TRUE, * uses_grouping = FALSE, * theme = "views_view" * ) */ class AgentMetricsExport extends DataExport { /** * {@inheritdoc} */ protected function defineOptions() { $options = parent::defineOptions(); $options['include_context'] = ['default' => TRUE]; $options['include_schema'] = ['default' => TRUE]; return $options; } /** * {@inheritdoc} */ public function buildOptionsForm(&$form, FormStateInterface $form_state) { parent::buildOptionsForm($form, $form_state); $form['include_context'] = [ '#type' => 'checkbox', '#title' => $this->t('Include @context (JSON-LD)'), '#default_value' => $this->options['include_context'], ]; $form['include_schema'] = [ '#type' => 'checkbox', '#title' => $this->t('Include Schema.org metadata'), '#default_value' => $this->options['include_schema'], ]; } /** * {@inheritdoc} */ public function render() { // Build JSON-LD structure $context = $this->options['include_context'] ? [ '@context' => 'https://schema.org/', '@type' => 'Collection', 'name' => $this->view->getTitle(), ] : []; $data = []; foreach ($this->view->result as $row) { $item = []; foreach ($this->view->field as $id => $field) { $item[$id] = $field->getItems($row); } if ($this->options['include_schema']) { $item['@type'] = 'AgentExecution'; $item['@context'] = 'https://schema.org/'; } $data[] = $item; } $output = array_merge($context, ['items' => $data]); // Return as JSON return json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); } }

Real-Time Views Updates

WebSocket Integration

Update views in real-time as agent data changes:

<?php namespace Drupal\my_module\Service; use Drupal\Core\Entity\EntityTypeManagerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Real-time views update service using WebSocket. */ class RealtimeViewsUpdater implements EventSubscriberInterface { protected $entityTypeManager; protected $websocketClient; public function __construct(EntityTypeManagerInterface $entity_type_manager) { $this->entityTypeManager = $entity_type_manager; } public static function getSubscribedEvents() { return [ 'ai_agent_execution.created' => 'onExecutionCreated', 'ai_agent_execution.updated' => 'onExecutionUpdated', 'ai_agent_execution.completed' => 'onExecutionCompleted', ]; } /** * Push new execution to all subscribed views. */ public function onExecutionCreated($event) { $execution = $event->getExecution(); $message = [ 'action' => 'add', 'view' => 'agent_executions', 'data' => [ 'id' => $execution->id(), 'agent_id' => $execution->getAgentId(), 'status' => $execution->getStatus(), 'created' => $execution->getCreatedTime(), ], ]; $this->broadcastToWebSocket($message); } /** * Push updated execution to views. */ public function onExecutionUpdated($event) { $execution = $event->getExecution(); $message = [ 'action' => 'update', 'view' => 'agent_executions', 'id' => $execution->id(), 'data' => [ 'status' => $execution->getStatus(), 'tokens_used' => $execution->getTokensUsed(), 'cost' => $execution->getCost(), ], ]; $this->broadcastToWebSocket($message); } /** * Send message to WebSocket subscribers. */ protected function broadcastToWebSocket(array $message) { // Use a WebSocket service or message queue $queue = \Drupal::queue('realtime_updates'); $queue->createItem($message); } }

JavaScript Real-Time Updates

// JavaScript for real-time view updates via WebSocket (function(Drupal, drupalSettings) { 'use strict'; Drupal.behaviors.agentRealtimeViews = { attach: function(context, settings) { const viewElement = once('agent-realtime-view', '[data-view-id="agent_executions"]', context); if (!viewElement.length) { return; } // Connect to WebSocket const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; const ws = new WebSocket(wsProtocol + '//' + location.host + '/agent-updates'); ws.onmessage = function(event) { const message = JSON.parse(event.data); switch (message.action) { case 'add': Drupal.behaviors.agentRealtimeViews.addRow(message.data); break; case 'update': Drupal.behaviors.agentRealtimeViews.updateRow(message.id, message.data); break; case 'remove': Drupal.behaviors.agentRealtimeViews.removeRow(message.id); break; } }; ws.onerror = function(error) { console.error('WebSocket error:', error); }; ws.onclose = function() { console.log('WebSocket disconnected'); // Reconnect after 5 seconds setTimeout(function() { Drupal.behaviors.agentRealtimeViews.attach(context, settings); }, 5000); }; }, addRow: function(data) { const tableBody = document.querySelector('[data-view-id="agent_executions"] tbody'); if (!tableBody) return; const row = document.createElement('tr'); row.dataset.executionId = data.id; row.innerHTML = ` <td>${data.agent_id}</td> <td><span class="status status-${data.status}">${data.status}</span></td> <td>${new Date(data.created).toLocaleString()}</td> `; tableBody.insertBefore(row, tableBody.firstChild); }, updateRow: function(id, data) { const row = document.querySelector(`tr[data-execution-id="${id}"]`); if (!row) return; if (data.status) { const statusCell = row.querySelector('td:nth-child(2)'); statusCell.innerHTML = `<span class="status status-${data.status}">${data.status}</span>`; } if (data.tokens_used) { const tokensCell = row.querySelector('td:nth-child(4)'); if (tokensCell) tokensCell.textContent = data.tokens_used; } if (data.cost) { const costCell = row.querySelector('td:nth-child(5)'); if (costCell) costCell.textContent = '$' + data.cost.toFixed(4); } }, removeRow: function(id) { const row = document.querySelector(`tr[data-execution-id="${id}"]`); if (row) row.remove(); } }; })(Drupal, drupalSettings);

Code Examples

Example 1: Complete Agent Execution View Configuration

YAML Configuration (can be stored in config/install/views.view.agent_executions.yml):

langcode: en status: true id: agent_executions label: 'Agent Executions' module: views description: 'Display AI agent execution history' tag: 'Agent Dashboard' base_table: ai_agent_executions base_field: id display: default: display_plugin: default id: default display_title: Master position: 0 display_options: title: 'Agent Executions' fields: id: id: id table: ai_agent_executions field: id label: 'Execution ID' agent_name: id: agent_name table: ai_agent_executions field: agent_name label: 'Agent Name' status: id: status table: ai_agent_executions field: status label: 'Status' tokens_used: id: tokens_used table: ai_agent_executions field: tokens_used label: 'Tokens Used' cost: id: cost table: ai_agent_executions field: cost label: 'Cost (USD)' type: numeric settings: precision: 2 created: id: created table: ai_agent_executions field: created label: 'Created' type: date settings: date_format: 'Y-m-d H:i' filters: status: id: status table: ai_agent_executions field: status operator: in value: completed: completed failed: failed group: 1 created: id: created table: ai_agent_executions field: created operator: '>=' value: '-7 days' sorts: created: id: created table: ai_agent_executions field: created order: DESC pager: type: full options: items_per_page: 25 style: type: table options: sticky: true columns: id: id agent_name: agent_name status: status tokens_used: tokens_used cost: cost created: created info: id: sortable: true default_sort_order: desc agent_name: sortable: true status: sortable: true tokens_used: sortable: true cost: sortable: true created: sortable: true page: display_plugin: page id: page display_title: Page position: 1 display_options: path: admin/agents/executions export_csv: display_plugin: views_data_export id: export_csv display_title: 'Export CSV' position: 2 display_options: path: agents/export/csv pager: type: some style: type: views_data_export_csv options: provide_file: true filename: 'agent-executions-[date:custom:Y-m-d-H-i].csv' export_json: display_plugin: views_data_export id: export_json display_title: 'Export JSON' position: 3 display_options: path: agents/export/json pager: type: some style: type: views_data_export_json options: provide_file: true filename: 'agent-executions-[date:custom:Y-m-d-H-i].json'

Example 2: Custom Field Handler with Formatting

<?php namespace Drupal\my_module\Plugin\views\field; use Drupal\views\Plugin\views\field\FieldPluginBase; use Drupal\views\ResultRow; /** * Display agent execution status with color coding. * * @ViewsField("agent_status_badge") */ class AgentStatusBadge extends FieldPluginBase { public function render(ResultRow $values) { $status = $values->{$this->field}; // Define status colors $status_colors = [ 'pending' => '#FFA500', 'running' => '#4CAF50', 'completed' => '#2196F3', 'failed' => '#F44336', ]; $color = $status_colors[$status] ?? '#999999'; return [ '#theme' => 'status_badge', '#status' => $status, '#color' => $color, '#attached' => [ 'library' => ['my_module/status-badge'], ], ]; } }

Template (status-badge.html.twig):

<span class="status-badge" style="background-color: {{ color }}; color: white; padding: 4px 8px; border-radius: 4px; display: inline-block;"> {{ status|upper }} </span>

Example 3: REST API Export Endpoint

Enable REST API views export:

langcode: en status: true module: rest id: agent_executions_rest label: 'Agent Executions REST' resource: 'views.agent_executions' granularity: resource configuration: methods: - GET formats: - json - xml authentication: - oauth2 - cookie

Access via:

GET /api/agent-executions?format=json
GET /api/agent-executions?format=xml

Best Practices

1. [object Object]

// Use efficient field handlers $data['ai_agent_executions']['tokens_used'] = [ 'field' => [ 'id' => 'numeric', 'no group by' => FALSE, // Allow grouping/aggregation ], ]; // Cache view results $cache_plugin = $view->getDisplay()->getPlugin('cache'); $cache_plugin->cacheSet([ 'max_age' => 300, // 5 minutes 'tags' => ['ai_agent_executions'], ]);

2. [object Object]

  • Always use access tag: 'access query tag' => 'ai_agent_executions_access'
  • Implement hook_query_TAG_alter() to enforce permissions
  • Validate user access before rendering sensitive data
function my_module_query_ai_agent_executions_access_alter(QueryAlterableInterface $query) { $user = \Drupal::currentUser(); // Only show own agent executions to non-admins if (!$user->hasPermission('view all agent executions')) { $query->condition('uid', $user->id()); } }

3. [object Object]

Use filters to ensure data integrity:

// Only show recent executions $data['ai_agent_executions']['created'] = [ 'filter' => [ 'id' => 'date', 'no group by' => FALSE, ], ];

4. [object Object]

For large datasets:

# Use LIMIT in views pager: type: mini options: items_per_page: 10 # Implement batch export display: export_batch: style: id: views_data_export options: batch_size: 1000 provide_file: true

5. [object Object]

  • Document custom field handlers with comments
  • Use consistent naming conventions
  • Create reusable field handlers for common patterns
  • Version your views configuration

Troubleshooting

Issue: View Not Displaying Data

Solution: Verify Views Data is properly defined:

// In module_name.views.inc function module_name_views_data() { // Check that table, fields, and handlers are defined // Verify field IDs match database columns // Test with: drush vd module_name.views_data }

Issue: Filter Not Working

Solution: Ensure filter uses correct operator and options:

$data['table']['field']['filter'] = [ 'id' => 'string', // Match field type 'operator' => 'in', // Verify operator is valid 'options' => [...], // Ensure options are defined ];

Issue: Export File Truncated

Solution: Increase batch size for large exports:

style: options: batch_size: 5000 # Increase from default 1000 use_batch: true

Issue: Real-Time Updates Not Working

Solution: Verify WebSocket connection and events:

# Check WebSocket server drush config:get system.performance # Verify events dispatched drush ev "print_r(\Drupal::service('event_dispatcher')->getListeners());" # Test WebSocket wscat -c ws://localhost/agent-updates

Issue: Performance Degradation

Solution: Implement caching and indexing:

// Add database indexes $schema['ai_agent_executions']['indexes'] = [ 'agent_id_status' => ['agent_id', 'status'], 'created' => ['created'], 'cost' => ['cost'], ]; // Cache view results $cache_tags = [ 'ai_agent_executions', 'ai_agent:' . $agent_id, ];


Summary

This guide provides comprehensive coverage of using Drupal Views for AI agent data display, including:

  • Views API fundamentals and plugin architecture
  • Patterns for displaying direct entity and custom data sources
  • Creating custom field, filter, and display plugins
  • Export formats: CSV, JSON, XML, XLS
  • Real-time updates via WebSocket
  • Complete code examples and best practices
  • Performance, security, and scalability considerations

For additional support or examples specific to your agents, refer to the individual module documentation or contact the development team.


Last Updated: 2026-01-08 Status: Complete Version: 1.0.0 Author: Agent 15 - Drupal Views AI Integration Researcher