Skip to content

Commit 36671ba

Browse files
Merge pull request #376 from JLG-WOCFR-DEV/codex/continue-developpement-prioritaire
Add image scan metrics instrumentation
2 parents ab77482 + 8ada603 commit 36671ba

File tree

3 files changed

+198
-6
lines changed

3 files changed

+198
-6
lines changed

docs/ameliorations-et-tests.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
## Fonctions prioritaires par rapport aux applications professionnelles
44

55
- **`blc_schedule_manual_image_scan`** – Contrairement aux scans de liens, aucun identifiant de job ni compteur de tentatives n'est stocké ; la fonction se contente de lancer `wp_schedule_single_event()` et force `is_full_scan` à `true`, ce qui empêche de distinguer un balayage partiel ou de suivre un historique des relances. Les plateformes pro conservent ces métadonnées pour la supervision et la reprise manuelle. 【F:liens-morts-detector-jlg/includes/blc-admin-pages.php†L381-L460】
6-
- **`blc_perform_image_check`** – Le contrôleur d'images est exécuté sans instrumentation ni hook de télémétrie. À la différence de `blc_perform_check()`, aucun métrique (durée, throughput, état du lot) n'est publié pour alimenter l'historique ou les dashboards externes, ce qui laisse l'équipe support sans visibilité en cas de scan long ou bloqué. 【F:liens-morts-detector-jlg/includes/blc-scanner.php†L3948-L4005
6+
- **`blc_perform_image_check`** – Le contrôleur d'images était exécuté sans instrumentation ni hook de télémétrie. À la différence de `blc_perform_check()`, aucun métrique (durée, throughput, état du lot) n'était publié pour alimenter l'historique ou les dashboards externes, ce qui laissait l'équipe support sans visibilité en cas de scan long ou bloqué. ✅ Ce manque est désormais comblé : la fonction publie des métriques structurées et un hook `blc_image_scan_metrics` alignés sur les scans de liens. 【F:liens-morts-detector-jlg/includes/blc-scanner.php†L4668-L4744
77
- **`blc_update_image_scan_status`** – La structure de statut n'expose ni `job_id` ni journal des transitions et ne notifie aucun hook dédié lors d'un changement d'état, ce qui complique l'audit et l'alignement avec les processus ITIL utilisés par les suites professionnelles. Harmoniser cette fonction avec `blc_update_link_scan_status()` (machine à états, journalisation) permettrait de tracer finement les scans d'images. 【F:liens-morts-detector-jlg/includes/blc-scanner.php†L867-L934】【F:liens-morts-detector-jlg/includes/blc-scanner.php†L557-L666】
88
- **`blc_update_image_scan_status`** – La structure de statut n'expose ni `job_id` ni journal des transitions et ne notifie aucun hook dédié lors d'un changement d'état, ce qui complique l'audit et l'alignement avec les processus ITIL utilisés par les suites professionnelles. Harmoniser cette fonction avec `blc_update_link_scan_status()` (machine à états, journalisation) permettrait de tracer finement les scans d'images. 【F:liens-morts-detector-jlg/includes/blc-scanner.php†L867-L934】【F:liens-morts-detector-jlg/includes/blc-scanner.php†L557-L666】
99
- **`blc_schedule_manual_image_scan`** – Contrairement au parcours des liens, la planification d'images ne retournait aucun identifiant de job ni tentative associée, empêchant de tracer les relances et de synchroniser l'interface avec le scan lancé. Une instrumentation équivalente est attendue côté solutions pro. 【F:liens-morts-detector-jlg/includes/blc-admin-pages.php†L1280-L1358】
@@ -25,6 +25,7 @@
2525

2626
> `blc_update_image_scan_status` conserve maintenant un journal d'audit (transitions valides/invalides), persiste l'historique des jobs et propage `job_id`, `attempt` et `scheduled_at` pour l'observabilité, offrant un suivi équivalent à celui des scans de liens. 【F:liens-morts-detector-jlg/includes/blc-scanner.php†L1109-L1285】【F:tests/LinkScanStatusTest.php†L294-L372】
2727
> `blc_schedule_manual_image_scan` génère un identifiant de job déterministe, retente la planification en cas d'échec, journalise le contexte et renvoie les métadonnées nécessaires à l'interface et aux appels AJAX. 【F:liens-morts-detector-jlg/includes/blc-admin-pages.php†L1280-L1366】【F:liens-morts-detector-jlg/includes/blc-admin-pages.php†L3698-L3771】
28+
> `blc_perform_image_check` journalise désormais la durée, l'état et la progression du lot traité, enregistre un historique `blc_image_scan_metrics_history` persistant et expose le hook `blc_image_scan_metrics` pour la télémétrie externe. 【F:liens-morts-detector-jlg/includes/blc-scanner.php†L742-L779】【F:liens-morts-detector-jlg/includes/blc-scanner.php†L4668-L4744】
2829
2930
## Tests manuels recommandés
3031

liens-morts-detector-jlg/includes/blc-scanner.php

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,46 @@ function blc_record_link_scan_metrics(array $metrics) {
739739
}
740740
}
741741

742+
if (!function_exists('blc_record_image_scan_metrics')) {
743+
/**
744+
* Store last image scan metrics for observability dashboards.
745+
*
746+
* @param array<string, mixed> $metrics
747+
*
748+
* @return void
749+
*/
750+
function blc_record_image_scan_metrics(array $metrics) {
751+
$history = get_option('blc_image_scan_metrics_history', []);
752+
if (!is_array($history)) {
753+
$history = [];
754+
}
755+
756+
array_unshift($history, $metrics);
757+
if (count($history) > 50) {
758+
$history = array_slice($history, 0, 50);
759+
}
760+
761+
update_option('blc_image_scan_metrics_history', $history, false);
762+
763+
if (isset($metrics['job_id'])) {
764+
blc_update_image_scan_history_entry($metrics['job_id'], ['metrics' => $metrics]);
765+
}
766+
}
767+
}
768+
769+
if (!function_exists('blc_get_image_scan_metrics_history')) {
770+
/**
771+
* Retrieve the persisted metrics history for image scans.
772+
*
773+
* @return array<int, array<string, mixed>>
774+
*/
775+
function blc_get_image_scan_metrics_history() {
776+
$history = get_option('blc_image_scan_metrics_history', []);
777+
778+
return is_array($history) ? $history : [];
779+
}
780+
}
781+
742782
if (!function_exists('blc_get_link_scan_metrics_history')) {
743783
/**
744784
* Retrieve the persisted metrics history for link scans.
@@ -4628,10 +4668,13 @@ function blc_perform_check($batch = 0, $is_full_scan = false, $bypass_rest_windo
46284668
function blc_perform_image_check($batch = 0, $is_full_scan = true) {
46294669
global $wpdb;
46304670

4671+
$execution_started_at = microtime(true);
46314672
$table_name = (isset($wpdb) && isset($wpdb->prefix))
46324673
? $wpdb->prefix . 'blc_broken_links'
46334674
: 'blc_broken_links';
46344675

4676+
$result = null;
4677+
46354678
if (function_exists('blc_ensure_broken_links_table_exists') && !blc_ensure_broken_links_table_exists()) {
46364679
$message = sprintf(
46374680
/* translators: %s: database table name. */
@@ -4652,15 +4695,52 @@ function blc_perform_image_check($batch = 0, $is_full_scan = true) {
46524695
}
46534696

46544697
if (class_exists('WP_Error')) {
4655-
return new \WP_Error('blc_missing_broken_links_table', $message);
4698+
$result = new \WP_Error('blc_missing_broken_links_table', $message);
4699+
} else {
4700+
$result = false;
46564701
}
4702+
} else {
4703+
$queue = blc_make_image_scan_queue();
4704+
$controller = blc_make_image_scan_controller($queue);
46574705

4658-
return false;
4706+
$result = $controller->run($batch, $is_full_scan);
4707+
}
4708+
4709+
if (function_exists('is_wp_error') && is_wp_error($result)) {
4710+
$message = $result->get_error_message();
4711+
4712+
blc_update_image_scan_status([
4713+
'state' => 'failed',
4714+
'last_error' => $message,
4715+
'message' => $message,
4716+
]);
46594717
}
46604718

4661-
$queue = blc_make_image_scan_queue();
4662-
$controller = blc_make_image_scan_controller($queue);
4719+
$status = blc_get_image_scan_status();
4720+
$duration_ms = (int) round((microtime(true) - $execution_started_at) * 1000);
4721+
$job_id = isset($status['job_id']) ? (string) $status['job_id'] : '';
4722+
4723+
$metrics = [
4724+
'job_id' => $job_id,
4725+
'batch' => (int) $batch,
4726+
'duration_ms' => $duration_ms,
4727+
'timestamp' => time(),
4728+
'state' => isset($status['state']) ? (string) $status['state'] : 'unknown',
4729+
'success' => !(function_exists('is_wp_error') && is_wp_error($result)) && (!isset($status['state']) || $status['state'] !== 'failed'),
4730+
'processed_batches' => isset($status['processed_batches']) ? (int) $status['processed_batches'] : 0,
4731+
'total_batches' => isset($status['total_batches']) ? (int) $status['total_batches'] : 0,
4732+
'processed_items' => isset($status['processed_items']) ? (int) $status['processed_items'] : 0,
4733+
'total_items' => isset($status['total_items']) ? (int) $status['total_items'] : 0,
4734+
'attempt' => isset($status['attempt']) ? (int) $status['attempt'] : 0,
4735+
'is_full_scan' => isset($status['is_full_scan']) ? (bool) $status['is_full_scan'] : (bool) $is_full_scan,
4736+
];
4737+
4738+
blc_record_image_scan_metrics($metrics);
46634739

4664-
return $controller->run($batch, $is_full_scan);
4740+
if (function_exists('do_action')) {
4741+
do_action('blc_image_scan_metrics', $metrics, $result);
4742+
}
4743+
4744+
return $result;
46654745
}
46664746

tests/BlcScannerTest.php

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
namespace {
4+
require_once __DIR__ . '/../vendor/autoload.php';
45
require_once __DIR__ . '/translation-stubs.php';
56

67
if (!class_exists('WP_Error')) {
@@ -72,6 +73,21 @@ function is_email($email)
7273
}
7374
}
7475

76+
if (!function_exists('esc_sql')) {
77+
function esc_sql($value)
78+
{
79+
if (is_array($value)) {
80+
return array_map('esc_sql', $value);
81+
}
82+
83+
if (is_float($value) || is_int($value)) {
84+
return $value;
85+
}
86+
87+
return addslashes((string) $value);
88+
}
89+
}
90+
7591

7692
if (!class_exists('WP_Query')) {
7793
class WP_Query
@@ -4035,6 +4051,101 @@ public function test_blc_perform_image_check_records_remote_cdn_when_option_enab
40354051
}
40364052
}
40374053

4054+
public function test_blc_perform_image_check_records_metrics_history_and_triggers_hook(): void
4055+
{
4056+
global $wpdb;
4057+
$wpdb = $this->createWpdbStub();
4058+
4059+
$initialStatus = blc_get_default_image_scan_status();
4060+
$initialStatus['state'] = 'running';
4061+
$initialStatus['job_id'] = 'img-job-123';
4062+
$initialStatus['attempt'] = 2;
4063+
$initialStatus['processed_batches'] = 1;
4064+
$initialStatus['total_batches'] = 4;
4065+
$initialStatus['processed_items'] = 10;
4066+
$initialStatus['total_items'] = 40;
4067+
$initialStatus['is_full_scan'] = true;
4068+
$this->options['blc_image_scan_status'] = $initialStatus;
4069+
$this->options['blc_image_scan_history'] = [];
4070+
4071+
$controllerResult = ['processed' => 40];
4072+
$controller = new class($controllerResult) {
4073+
/** @var array<string, mixed> */
4074+
private array $result;
4075+
4076+
/**
4077+
* @param array<string, mixed> $result
4078+
*/
4079+
public function __construct(array $result)
4080+
{
4081+
$this->result = $result;
4082+
}
4083+
4084+
/**
4085+
* @param int $batch
4086+
* @param bool $is_full_scan
4087+
*
4088+
* @return array<string, mixed>
4089+
*/
4090+
public function run($batch, $is_full_scan)
4091+
{
4092+
blc_update_image_scan_status([
4093+
'state' => 'completed',
4094+
'processed_batches' => 4,
4095+
'total_batches' => 4,
4096+
'processed_items' => 40,
4097+
'total_items' => 40,
4098+
'message' => 'Scan terminé',
4099+
]);
4100+
4101+
return $this->result;
4102+
}
4103+
};
4104+
4105+
\Brain\Monkey\Functions\when('blc_make_image_scan_controller')->alias(fn($queue) => $controller);
4106+
4107+
$result = blc_perform_image_check(0, true);
4108+
4109+
$this->assertSame($controllerResult, $result, 'Le contrôleur stub doit retourner le payload attendu.');
4110+
4111+
$this->assertArrayHasKey('blc_image_scan_metrics_history', $this->updatedOptions, 'Les métriques doivent être historisées.');
4112+
$history = $this->updatedOptions['blc_image_scan_metrics_history'];
4113+
$this->assertIsArray($history);
4114+
$this->assertNotEmpty($history, 'Le premier lot doit enregistrer une entrée de métriques.');
4115+
$metrics = $history[0];
4116+
$this->assertSame('img-job-123', $metrics['job_id']);
4117+
$this->assertSame(0, $metrics['batch']);
4118+
$this->assertArrayHasKey('duration_ms', $metrics);
4119+
$this->assertIsInt($metrics['duration_ms']);
4120+
$this->assertGreaterThanOrEqual(0, $metrics['duration_ms']);
4121+
$this->assertLessThan(5000, $metrics['duration_ms']);
4122+
$this->assertSame($this->utcNow, $metrics['timestamp']);
4123+
$this->assertSame('completed', $metrics['state']);
4124+
$this->assertTrue($metrics['success']);
4125+
$this->assertSame(4, $metrics['processed_batches']);
4126+
$this->assertSame(4, $metrics['total_batches']);
4127+
$this->assertSame(40, $metrics['processed_items']);
4128+
$this->assertSame(40, $metrics['total_items']);
4129+
$this->assertSame(2, $metrics['attempt']);
4130+
$this->assertTrue($metrics['is_full_scan']);
4131+
4132+
$actions = array_filter(
4133+
$this->triggeredActions,
4134+
static fn(array $action) => $action['hook'] === 'blc_image_scan_metrics'
4135+
);
4136+
$this->assertNotEmpty($actions, 'Le hook blc_image_scan_metrics doit être déclenché.');
4137+
$lastAction = array_pop($actions);
4138+
$this->assertSame($metrics, $lastAction['args'][0]);
4139+
$this->assertSame($controllerResult, $lastAction['args'][1]);
4140+
4141+
$this->assertArrayHasKey('blc_image_scan_history', $this->options, 'Le journal des jobs doit être maintenu.');
4142+
$historyEntries = $this->options['blc_image_scan_history'];
4143+
$this->assertNotEmpty($historyEntries);
4144+
$this->assertSame('img-job-123', $historyEntries[0]['job_id']);
4145+
$this->assertArrayHasKey('metrics', $historyEntries[0], 'Les métriques doivent être attachées à l’entrée du job.');
4146+
$this->assertSame($metrics, $historyEntries[0]['metrics']);
4147+
}
4148+
40384149
public function test_blc_perform_image_check_detects_missing_srcset_variant(): void
40394150
{
40404151
global $wpdb;

0 commit comments

Comments
 (0)