ai_upgrade_assistant-0.2.0-alpha2/src/Controller/AnalysisController.php
src/Controller/AnalysisController.php
<?php
namespace Drupal\ai_upgrade_assistant\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Batch\BatchBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Drupal\ai_upgrade_assistant\Service\BatchAnalyzer;
use Drupal\Core\State\StateInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
/**
* Controller for handling analysis operations.
*/
class AnalysisController extends ControllerBase {
use DependencySerializationTrait;
/**
* The batch analyzer service.
*
* @var \Drupal\ai_upgrade_assistant\Service\BatchAnalyzer
*/
protected $batchAnalyzer;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* Constructs a new AnalysisController object.
*
* @param \Drupal\ai_upgrade_assistant\Service\BatchAnalyzer $batch_analyzer
* The batch analyzer service.
* @param \Drupal\Core\State\StateInterface $state
* The state service.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
*/
public function __construct(
BatchAnalyzer $batch_analyzer,
StateInterface $state,
RendererInterface $renderer,
DateFormatterInterface $date_formatter
) {
$this->batchAnalyzer = $batch_analyzer;
$this->state = $state;
$this->renderer = $renderer;
$this->dateFormatter = $date_formatter;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('ai_upgrade_assistant.batch_analyzer'),
$container->get('state'),
$container->get('renderer'),
$container->get('date.formatter')
);
}
/**
* Starts the analysis process.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse
* JSON response with batch token or redirect to batch processing.
*/
public function startAnalysis() {
// Clear any existing analysis data
$this->state->delete('ai_upgrade_assistant.analysis_results');
// Get list of enabled modules
$modules = array_keys($this->moduleHandler()->getModuleList());
// Create and set the batch
$batch = $this->batchAnalyzer->createBatch($modules);
// Set the batch
batch_set($batch);
// Process the batch
if ($this->isAjax()) {
$batch =& batch_get();
$batch['progressive'] = TRUE;
$response = [
'status' => 'success',
'message' => $this->t('Analysis started'),
'batch_token' => $batch['token'],
];
return new JsonResponse($response);
}
return batch_process('/admin/reports/ai-upgrade-assistant/analysis');
}
/**
* Checks if the current request is an AJAX request.
*
* @return bool
* TRUE if the request is an AJAX request, FALSE otherwise.
*/
protected function isAjax() {
return \Drupal::request()->isXmlHttpRequest();
}
/**
* Batch finished callback.
*/
public function analysisFinished($success, $results, $operations) {
if ($success) {
$this->messenger()->addStatus($this->t('Module analysis completed successfully.'));
}
else {
$this->messenger()->addError($this->t('There was an error during the analysis process.'));
}
}
/**
* Gets the current analysis progress.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* JSON response with progress information.
*/
public function getProgress() {
$batch = batch_get();
$results = $this->state->get('ai_upgrade_assistant.analysis_results', []);
if (!$batch) {
return new JsonResponse([
'status' => 'complete',
'current' => 100,
'total' => 100,
'message' => $this->t('Analysis complete'),
]);
}
$current = $batch['sets'][$batch['current_set']]['current'];
$total = $batch['sets'][$batch['current_set']]['total'];
return new JsonResponse([
'status' => 'in_progress',
'current' => $current,
'total' => $total,
'message' => $batch['sets'][$batch['current_set']]['message'],
'terminal_output' => $this->getLatestTerminalOutput(),
]);
}
/**
* Gets the latest recommendations.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* JSON response with rendered recommendations.
*/
public function getRecommendations() {
$results = $this->state->get('ai_upgrade_assistant.analysis_results', []);
$build = [
'#theme' => 'upgrade_recommendations',
'#recommendations' => $this->processResults($results),
];
return new JsonResponse([
'content' => $this->renderer->render($build),
]);
}
/**
* Gets the code diff view.
*
* @return array
* Render array for the diff view.
*/
public function getDiffView($file_path) {
$results = $this->state->get('ai_upgrade_assistant.analysis_results', []);
$file_results = $results['files'][$file_path] ?? [];
return [
'#theme' => 'code_diff_view',
'#file_path' => $file_path,
'#analysis' => $file_results,
'#attached' => [
'library' => ['ai_upgrade_assistant/diff_view'],
],
];
}
/**
* Gets the latest terminal output.
*
* @return string
* The latest terminal output.
*/
protected function getLatestTerminalOutput() {
return $this->state->get('ai_upgrade_assistant.terminal_output', '');
}
/**
* Processes analysis results into recommendations.
*
* @param array $results
* Raw analysis results.
*
* @return array
* Processed recommendations.
*/
protected function processResults(array $results) {
$recommendations = [];
// Process core analysis
if (!empty($results['core'])) {
if (!$results['core']['compatible']) {
$recommendations[] = [
'type' => 'core_upgrade',
'priority' => 'high',
'message' => $this->t('Upgrade to Drupal 10 required. Current version: @version',
['@version' => $results['core']['version']]),
];
}
}
// Process module analysis
if (!empty($results['modules'])) {
foreach ($results['modules'] as $name => $module) {
if (!$module['compatible']) {
$recommendations[] = [
'type' => 'module_compatibility',
'priority' => 'medium',
'message' => $this->t('Module @name needs to be updated for Drupal 10 compatibility',
['@name' => $name]),
'details' => $module['issues'],
];
}
}
}
// Process file analysis
if (!empty($results['files'])) {
foreach ($results['files'] as $file_path => $analysis) {
if (!empty($analysis['issues'])) {
foreach ($analysis['issues'] as $issue) {
$recommendations[] = [
'type' => $issue['type'],
'priority' => $issue['priority'],
'message' => $issue['description'],
'actions' => [
[
'label' => $this->t('View changes'),
'url' => "admin/reports/upgrade-assistant/diff/" . urlencode($file_path),
],
],
'code_example' => $issue['code_example'] ?? NULL,
];
}
}
}
}
return $recommendations;
}
/**
* Analyzes a specific module.
*
* @param string $module
* The machine name of the module to analyze.
*
* @return array|\Symfony\Component\HttpFoundation\RedirectResponse
* A render array or redirect response.
*/
public function analyzeModule($module) {
// Create batch with single module
$batch = $this->batchAnalyzer->createBatch([$module]);
batch_set($batch);
if (PHP_SAPI === 'cli') {
drush_backend_batch_process();
return [
'#markup' => $this->t('Module analysis complete.'),
];
}
return batch_process("/admin/reports/upgrade-assistant/module/$module");
}
/**
* Displays detailed analysis results for a module.
*
* @param string $module
* The machine name of the module.
*
* @return array
* A render array for the module details page.
*/
public function moduleDetails($module) {
$analysis_results = $this->state->get("ai_upgrade_assistant.analysis_results.$module", []);
return [
'#theme' => 'analysis_report',
'#module' => $module,
'#results' => $analysis_results,
'#attached' => [
'library' => ['ai_upgrade_assistant/analysis_report'],
],
];
}
/**
* Displays analysis results for a specific module.
*
* @param string $module
* The machine name of the module.
*
* @return array
* A render array for the analysis results page.
*/
public function displayAnalysisResults($module) {
$results = $this->state->get('ai_upgrade_assistant.analysis_results', []);
// If no results, try to analyze the module first
if (empty($results[$module])) {
try {
$project_analyzer = \Drupal::service('ai_upgrade_assistant.project_analyzer');
$analysis = $project_analyzer->analyzeModule($module);
// Store results in state
$results[$module] = $analysis;
$this->state->set('ai_upgrade_assistant.analysis_results', $results);
// Log success
$this->getLogger('ai_upgrade_assistant')->info('Successfully analyzed module @module', ['@module' => $module]);
}
catch (\Exception $e) {
// Log error
$this->getLogger('ai_upgrade_assistant')->error('Error analyzing module @module: @error', [
'@module' => $module,
'@error' => $e->getMessage(),
]);
// Show error message to user
$this->messenger()->addError($this->t('Error analyzing module: @error', ['@error' => $e->getMessage()]));
// Return empty analysis with error state
return [
'#theme' => 'ai_upgrade_assistant_analysis',
'#analysis' => [
'generated_on' => date('Y-m-d H:i:s'),
'drupal_version' => \Drupal::VERSION,
'status' => 'error',
'error_message' => $e->getMessage(),
],
'#attached' => [
'library' => ['ai_upgrade_assistant/analysis_report'],
],
];
}
}
$module_results = $results[$module] ?? [];
// Add debug output
$this->getLogger('ai_upgrade_assistant')->debug('Analysis results for @module: @results', [
'@module' => $module,
'@results' => print_r($module_results, TRUE),
]);
// Format the analysis results for the template while preserving all AI details
$formatted_results = [
'generated_on' => date('Y-m-d H:i:s'),
'drupal_version' => \Drupal::VERSION,
'status' => 'success',
'total_modules' => 1,
'issues_found' => count($module_results['issues'] ?? []),
'critical_issues' => count(array_filter($module_results['issues'] ?? [], function($issue) {
return isset($issue['severity']) && $issue['severity'] === 'critical';
})),
'compatibility' => $module_results['compatibility_score'] ?? 0,
'compatibility_factors' => $module_results['compatibility_factors'] ?? [],
'modules' => [
[
'name' => $module,
'version' => $module_results['version'] ?? 'unknown',
'status' => $module_results['status'] ?? 'unknown',
'description' => $module_results['description'] ?? '',
'project_url' => $module_results['project_url'] ?? '',
'issues' => array_map(function($issue) {
return [
'severity' => $issue['severity'] ?? 'notice',
'severity_icon' => $this->getSeverityIcon($issue['severity'] ?? 'notice'),
'title' => $issue['title'] ?? $issue['message'] ?? '',
'message' => $issue['message'] ?? '',
'file' => $issue['file'] ?? '',
'solution' => $issue['solution'] ?? '',
];
}, $module_results['issues'] ?? []),
'code_quality' => [
'coding_standards' => array_map(function($issue) {
return [
'message' => $issue['message'] ?? '',
'file' => $issue['file'] ?? '',
'line' => $issue['line'] ?? '',
'solution' => $issue['solution'] ?? '',
];
}, $module_results['code_quality']['coding_standards'] ?? []),
'deprecated_code' => array_map(function($issue) {
return [
'message' => $issue['message'] ?? '',
'file' => $issue['file'] ?? '',
'line' => $issue['line'] ?? '',
'replacement' => $issue['replacement'] ?? '',
];
}, $module_results['code_quality']['deprecated_code'] ?? []),
],
],
],
'ai_recommendation' => [
'greeting' => $module_results['ai_recommendation']['greeting'] ?? '',
'message' => $module_results['ai_recommendation']['message'] ?? '',
'next_steps' => $module_results['ai_recommendation']['next_steps'] ?? [],
'time_estimate' => [
'min' => $module_results['ai_recommendation']['time_estimate']['min'] ?? 0,
'max' => $module_results['ai_recommendation']['time_estimate']['max'] ?? 0,
],
'confidence' => $module_results['ai_recommendation']['confidence'] ?? 'medium',
],
];
return [
'#theme' => 'ai_upgrade_assistant_analysis',
'#analysis' => $formatted_results,
'#attached' => [
'library' => ['ai_upgrade_assistant/analysis_report'],
],
];
}
/**
* Gets the icon for a severity level.
*
* @param string $severity
* The severity level.
*
* @return string
* The material icon name.
*/
protected function getSeverityIcon($severity) {
switch ($severity) {
case 'critical':
return 'error';
case 'error':
return 'warning';
case 'warning':
return 'info';
default:
return 'check_circle';
}
}
/**
* Displays the analysis report.
*
* @return array
* A render array representing the analysis report.
*/
public function displayReport() {
$results = $this->state->get('ai_upgrade_assistant.analysis_results', []);
// Process the results
$summary = $this->calculateSummary($results);
$moduleAnalysis = $this->processModuleAnalysis($results);
$securityIssues = $this->processSecurityIssues($results);
// Structure the analysis data
$analysis = [
'generated_on' => [
'#markup' => $this->dateFormatter->format(time(), 'custom', 'Y-m-d H:i:s T'),
],
'drupal_version' => [
'#markup' => \Drupal::VERSION,
],
'total_modules' => [
'#markup' => $summary['total_files'] ?? 0,
],
'issues_found' => [
'#markup' => $summary['total_issues'] ?? 0,
],
'critical_issues' => [
'#markup' => $summary['critical_issues'] ?? 0,
],
'compatibility' => [
'#markup' => isset($summary['total_files']) && $summary['total_files'] > 0
? round(100 - (($summary['total_issues'] / $summary['total_files']) * 100))
: 0,
],
'modules' => array_map(function($module) {
return [
'name' => ['#markup' => $module['name']],
'version' => ['#markup' => $module['version']],
'status' => ['#markup' => $module['status']],
'issues' => array_map(function($issue) {
return [
'type' => ['#markup' => $issue['type'] ?? 'unknown'],
'message' => ['#markup' => $issue['message'] ?? ''],
'severity' => ['#markup' => $issue['severity'] ?? 'info'],
];
}, $module['issues'] ?? []),
];
}, $moduleAnalysis),
'security_issues' => array_map(function($issue) {
return [
'module' => ['#markup' => $issue['module']],
'title' => ['#markup' => $issue['title']],
'severity' => ['#markup' => $issue['severity']],
'description' => ['#markup' => $issue['description']],
'solution' => ['#markup' => $issue['solution'] ?? ''],
];
}, $securityIssues),
];
return [
'#theme' => 'ai_upgrade_assistant_analysis',
'#analysis' => $analysis,
'#attached' => [
'library' => ['ai_upgrade_assistant/analysis_report'],
],
];
}
/**
* Calculates summary statistics from analysis results.
*/
protected function calculateSummary($results) {
$summary = [
'total_files' => 0,
'total_issues' => 0,
'critical_issues' => 0,
'warnings' => 0,
'suggestions' => 0,
];
if (!empty($results['files'])) {
$summary['total_files'] = count($results['files']);
foreach ($results['files'] as $file) {
if (!empty($file['issues'])) {
foreach ($file['issues'] as $issue) {
$summary['total_issues']++;
switch ($issue['severity']) {
case 'critical':
$summary['critical_issues']++;
break;
case 'warning':
$summary['warnings']++;
break;
case 'suggestion':
$summary['suggestions']++;
break;
}
}
}
}
}
return $summary;
}
/**
* Processes module analysis results.
*/
protected function processModuleAnalysis($results) {
$moduleAnalysis = [];
if (!empty($results['modules'])) {
foreach ($results['modules'] as $name => $module) {
$moduleAnalysis[$name] = [
'name' => $name,
'version' => $module['version'] ?? 'Unknown',
'status' => $module['compatible'] ? 'Compatible' : 'Needs Update',
'issues' => $module['issues'] ?? [],
'dependencies' => $module['dependencies'] ?? [],
'security_issues' => $module['security_issues'] ?? [],
'available_updates' => $module['available_updates'] ?? [],
];
}
}
return $moduleAnalysis;
}
/**
* Processes security issues from results.
*/
protected function processSecurityIssues($results) {
$securityIssues = [];
if (!empty($results['security'])) {
foreach ($results['security'] as $module => $issues) {
foreach ($issues as $issue) {
$securityIssues[] = [
'module' => $module,
'title' => $issue['title'],
'severity' => $issue['severity'],
'description' => $issue['description'],
'solution' => $issue['solution'] ?? '',
'advisory_link' => $issue['link'] ?? '',
];
}
}
}
return $securityIssues;
}
}
