Forráskód Böngészése

fix(ui): policy threshold matrix off-by-one on edit render

The edit page built `thresholds_by_id` via Twig's |merge with integer
category-id keys. |merge calls PHP's array_merge, which renumbers
integer keys to a 0-based sequence — so the lookup map ended up keyed
0..N-1 instead of by category id, and each row pulled the next row's
value (last row blank). Switch the lookup to be keyed by the category
slug (a string), which array_merge preserves verbatim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chiappa 1 hete
szülő
commit
af5be88554

+ 7 - 3
ui/resources/views/pages/policies/edit.twig

@@ -3,9 +3,13 @@
 {% block title %}{{ policy.name }} — Policy — IRDB{% endblock %}
 
 {% block content %}
-{% set thresholds_by_id = {} %}
+{# Twig's |merge calls PHP array_merge, which renumbers integer keys; key by
+   slug (a string) so {scanners:40, indexer:30,…} round-trips faithfully. #}
+{% set thresholds_by_slug = {} %}
 {% for t in policy.thresholds|default([]) %}
-    {% set thresholds_by_id = thresholds_by_id|merge({(t.category_id): t.threshold}) %}
+    {% if t.category_slug %}
+        {% set thresholds_by_slug = thresholds_by_slug|merge({(t.category_slug): t.threshold}) %}
+    {% endif %}
 {% endfor %}
 
 <div class="mx-auto max-w-5xl">
@@ -72,7 +76,7 @@
                             <td class="py-2 text-right">
                                 <input type="number" step="0.01" min="0"
                                        name="thresholds[{{ c.slug }}]"
-                                       value="{{ thresholds_by_id[c.id]|default('') }}"
+                                       value="{{ thresholds_by_slug[c.slug]|default('') }}"
                                        {% if not can_write %}readonly{% endif %}
                                        class="w-32 rounded-md border border-slate-300 bg-white px-2 py-1 text-right font-mono dark:border-slate-700 dark:bg-slate-950">
                             </td>

+ 42 - 0
ui/tests/Integration/Crud/CrudPagesTest.php

@@ -69,6 +69,48 @@ final class CrudPagesTest extends AppTestCase
         self::assertStringContainsString('moderate', (string) $response->getBody());
     }
 
+    public function testPolicyEditAlignsThresholdsToCategoryRows(): void
+    {
+        // Regression: |merge of {(int_id): val} renumbered the keys via
+        // PHP array_merge, shifting each rendered value up one row and
+        // leaving the last category blank. Use category IDs that are not
+        // a 0-based sequence so any reindexing is observable.
+        $this->enqueueApiResponse(200, [
+            'id' => 1, 'name' => 'custom', 'description' => null,
+            'include_manual_blocks' => true,
+            'thresholds' => [
+                ['category_id' => 11, 'category_slug' => 'scanners', 'threshold' => 40.0],
+                ['category_id' => 12, 'category_slug' => 'indexer', 'threshold' => 30.0],
+                ['category_id' => 13, 'category_slug' => 'malicious', 'threshold' => 20.0],
+                ['category_id' => 14, 'category_slug' => 'load', 'threshold' => 10.0],
+            ],
+            'created_at' => '2026-04-29T10:00:00Z',
+        ]);
+        $this->enqueueApiResponse(200, ['items' => [
+            ['id' => 11, 'slug' => 'scanners', 'name' => 'Scanners', 'description' => null, 'decay_function' => 'exponential', 'decay_param' => 14.0, 'is_active' => true],
+            ['id' => 12, 'slug' => 'indexer', 'name' => 'Indexer', 'description' => null, 'decay_function' => 'exponential', 'decay_param' => 14.0, 'is_active' => true],
+            ['id' => 13, 'slug' => 'malicious', 'name' => 'Malicious', 'description' => null, 'decay_function' => 'exponential', 'decay_param' => 14.0, 'is_active' => true],
+            ['id' => 14, 'slug' => 'load', 'name' => 'Load', 'description' => null, 'decay_function' => 'exponential', 'decay_param' => 14.0, 'is_active' => true],
+        ], 'total' => 4]);
+
+        $response = $this->request('GET', '/app/policies/1');
+        self::assertSame(200, $response->getStatusCode());
+        $body = (string) $response->getBody();
+
+        foreach ([
+            ['scanners', '40'],
+            ['indexer', '30'],
+            ['malicious', '20'],
+            ['load', '10'],
+        ] as [$slug, $val]) {
+            self::assertMatchesRegularExpression(
+                '/name="thresholds\[' . preg_quote($slug, '/') . '\]"[^>]*value="' . $val . '"/',
+                $body,
+                "row {$slug} must render value {$val}",
+            );
+        }
+    }
+
     public function testReportersListRenders(): void
     {
         $this->enqueueApiResponse(200, ['data' => [