ai_upgrade_assistant-0.2.0-alpha2/src/Service/AnalysisTracker.php
src/Service/AnalysisTracker.php
<?php
namespace Drupal\ai_upgrade_assistant\Service;
use Drupal\Core\State\StateInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\Queue\QueueInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\ai_upgrade_assistant\Service\HuggingFaceService;
/**
* Service for tracking module analysis history and status.
*/
class AnalysisTracker {
use DependencySerializationTrait;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* The logger factory.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
protected $loggerFactory;
/**
* The cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cache;
/**
* The queue service.
*
* @var \Drupal\Core\Queue\QueueInterface
*/
protected $queue;
/**
* The HuggingFace service.
*
* @var \Drupal\ai_upgrade_assistant\Service\HuggingFaceService
*/
protected $huggingFace;
/**
* Constructs a new AnalysisTracker.
*
* @param \Drupal\Core\State\StateInterface $state
* The state service.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* The logger factory service.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache backend.
* @param \Drupal\Core\Queue\QueueFactory $queue_factory
* The queue factory service.
* @param \Drupal\ai_upgrade_assistant\Service\HuggingFaceService $hugging_face
* The HuggingFace service.
*/
public function __construct(
StateInterface $state,
LoggerChannelFactoryInterface $logger_factory,
CacheBackendInterface $cache,
QueueFactory $queue_factory,
HuggingFaceService $hugging_face
) {
$this->state = $state;
$this->loggerFactory = $logger_factory;
$this->cache = $cache;
$this->queue = $queue_factory->get('ai_upgrade_assistant_analysis');
$this->huggingFace = $hugging_face;
}
/**
* Initializes a new analysis for a module.
*
* @param string $module
* The module being analyzed.
* @param array $context
* Additional context about the analysis.
*
* @return array
* The initialized analysis record.
*/
public function initializeAnalysis($module, array $context = []) {
$analysis = [
'module' => $module,
'start_time' => time(),
'status' => 'initialized',
'context' => $context,
'results' => [
'code_analysis' => [],
'dependency_analysis' => [],
'patches' => [],
'security_analysis' => [],
],
];
// Clear any previous in-progress analysis
$this->clearInProgressAnalysis($module);
// Save the new analysis
$history = $this->getAnalysisHistory($module);
array_unshift($history, $analysis);
$this->saveAnalysisHistory($module, $history);
return $analysis;
}
/**
* Clears any in-progress analysis for a module.
*
* @param string $module
* The module name.
*/
protected function clearInProgressAnalysis($module) {
$history = $this->getAnalysisHistory($module);
// Remove any in-progress analysis
foreach ($history as $key => $analysis) {
if (isset($analysis['status']) && $analysis['status'] === 'in_progress') {
unset($history[$key]);
}
}
$this->saveAnalysisHistory($module, array_values($history));
}
/**
* Updates an analysis record with new data.
*
* @param string $module
* The module being analyzed.
* @param array $data
* The data to update.
* @param string $section
* The section to update (e.g., 'code_analysis', 'dependency_analysis').
*/
public function updateAnalysis($module, array $data, $section) {
$history = $this->getAnalysisHistory($module);
if (!empty($history)) {
if (!isset($history[0]['results'][$section])) {
$history[0]['results'][$section] = [];
}
$history[0]['results'][$section] = array_merge($history[0]['results'][$section], $data);
$this->saveAnalysisHistory($module, $history);
}
}
/**
* Records the start of an analysis.
*
* @param string $module
* The module being analyzed.
* @param string $type
* The type of analysis (e.g., 'drupal11', 'minor_update').
* @param array $context
* Additional context about the analysis.
*/
public function startAnalysis($module, $type, array $context = []) {
$history = $this->getAnalysisHistory($module);
$analysis = [
'type' => $type,
'start_time' => time(),
'status' => 'in_progress',
'context' => $context,
];
array_unshift($history, $analysis);
$this->saveAnalysisHistory($module, $history);
}
/**
* Records the completion of an analysis.
*
* @param string $module
* The module being analyzed.
* @param array $results
* The analysis results.
* @param bool $success
* Whether the analysis was successful.
*/
public function completeAnalysis($module, array $results, $success = TRUE) {
$history = $this->getAnalysisHistory($module);
if (!empty($history)) {
$history[0]['end_time'] = time();
$history[0]['status'] = $success ? 'completed' : 'failed';
$history[0]['results'] = $results;
$this->saveAnalysisHistory($module, $history);
}
}
/**
* Gets the analysis history for a module.
*
* @param string $module
* The module name.
*
* @return array
* The analysis history.
*/
public function getAnalysisHistory($module) {
$key = "ai_upgrade_assistant.analysis_history.$module";
return $this->state->get($key, []);
}
/**
* Saves the analysis history for a module.
*
* @param string $module
* The module name.
* @param array $history
* The analysis history to save.
*/
protected function saveAnalysisHistory($module, array $history) {
$key = "ai_upgrade_assistant.analysis_history.$module";
// Keep only the last 10 analyses
$history = array_slice($history, 0, 10);
$this->state->set($key, $history);
}
/**
* Gets the last analysis time for a module.
*
* @param string $module
* The module name.
* @param string $type
* Optional analysis type to filter by.
*
* @return int|null
* The timestamp of the last analysis, or NULL if never analyzed.
*/
public function getLastAnalysisTime($module, $type = NULL) {
$history = $this->getAnalysisHistory($module);
if (empty($history)) {
return NULL;
}
if ($type) {
foreach ($history as $analysis) {
if ($analysis['type'] === $type && $analysis['status'] === 'completed') {
return $analysis['end_time'];
}
}
return NULL;
}
return $history[0]['end_time'] ?? NULL;
}
/**
* Checks if a module needs reanalysis.
*
* @param string $module
* The module name.
* @param string $type
* The type of analysis.
*
* @return bool
* TRUE if the module should be reanalyzed.
*/
public function needsReanalysis($module, $type) {
$config = \Drupal::config('ai_upgrade_assistant.settings');
$recheck_interval = $config->get('recheck_interval') ?? 604800; // Default 1 week
$last_analysis = $this->getLastAnalysisTime($module, $type);
if (!$last_analysis) {
return TRUE;
}
return (time() - $last_analysis) > $recheck_interval;
}
/**
* Gets analysis statistics.
*
* @return array
* Statistics about analyses performed.
*/
public function getAnalysisStats() {
$modules = \Drupal::service('module_handler')->getModuleList();
$stats = [
'total_analyzed' => 0,
'needs_reanalysis' => 0,
'never_analyzed' => 0,
'last_analysis' => NULL,
'by_type' => [],
];
foreach ($modules as $module => $info) {
$history = $this->getAnalysisHistory($module);
if (!empty($history)) {
$stats['total_analyzed']++;
if ($this->needsReanalysis($module, $history[0]['type'])) {
$stats['needs_reanalysis']++;
}
foreach ($history as $analysis) {
$type = $analysis['type'];
if (!isset($stats['by_type'][$type])) {
$stats['by_type'][$type] = 0;
}
$stats['by_type'][$type]++;
}
// Track most recent analysis
$end_time = $history[0]['end_time'] ?? NULL;
if ($end_time && (!$stats['last_analysis'] || $end_time > $stats['last_analysis'])) {
$stats['last_analysis'] = $end_time;
}
}
else {
$stats['never_analyzed']++;
}
}
return $stats;
}
/**
* Saves analysis results.
*
* @param array $results
* Analysis results to save. Should contain:
* - timestamp: Unix timestamp when analysis completed
* - total_files: Total number of files analyzed
* - files_processed: Number of files successfully processed
* - results: Array of analysis results per file
* - errors: Array of errors encountered during analysis
*/
public function saveResults(array $results) {
// Validate required fields
$required_fields = ['timestamp', 'total_files', 'files_processed', 'results', 'errors'];
foreach ($required_fields as $field) {
if (!isset($results[$field])) {
$this->loggerFactory->get('ai_upgrade_assistant')->error(
'Missing required field @field in analysis results',
['@field' => $field]
);
return;
}
}
// Add metadata
$results['metadata'] = [
'version' => '1.0',
'generated' => date('c', $results['timestamp']),
'duration' => isset($results['start_time']) ? ($results['timestamp'] - $results['start_time']) : 0,
];
// Save to state
$this->state->set('ai_upgrade_assistant.analysis_results', $results);
$this->state->set('ai_upgrade_assistant.last_analysis', $results['timestamp']);
// Log success
$this->loggerFactory->get('ai_upgrade_assistant')->info(
'Analysis results saved. Processed @processed of @total files with @error_count errors.',
[
'@processed' => $results['files_processed'],
'@total' => $results['total_files'],
'@error_count' => count($results['errors']),
]
);
}
/**
* Gets saved analysis results.
*
* @return array
* Saved analysis results.
*/
public function getResults() {
return $this->state->get('ai_upgrade_assistant.analysis_results', []);
}
/**
* Gets the timestamp of the last analysis.
*
* @return int|null
* Unix timestamp of the last analysis, or NULL if no analysis has been run.
*/
public function getLastAnalysisTimeGlobal() {
return $this->state->get('ai_upgrade_assistant.last_analysis');
}
/**
* Clears saved analysis results.
*/
public function clearResults() {
$this->state->delete('ai_upgrade_assistant.analysis_results');
$this->state->delete('ai_upgrade_assistant.last_analysis');
$this->loggerFactory->get('ai_upgrade_assistant')->info('Analysis results cleared.');
}
}
