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
- Overview
- Views API Fundamentals
- AI Data Display Patterns
- Custom Views Plugins
- Data Export Formats
- Real-Time Views Updates
- Code Examples
- Best Practices
- 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:
- Implement
ViewsDataInterfaceto expose custom data - Create custom field handlers
- 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, ];
Related Resources
- Drupal Views API Documentation
- Views Data Export Module
- Custom Field Plugin Template
- AI Agent Orchestra Integration
- Charts AI Analytics
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