Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package openQA for openSUSE:Factory checked 
in at 2025-12-01 11:12:56
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/openQA (Old)
 and      /work/SRC/openSUSE:Factory/.openQA.new.14147 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "openQA"

Mon Dec  1 11:12:56 2025 rev:777 rq:1320530 version:5.1764349525.ffb59486

Changes:
--------
--- /work/SRC/openSUSE:Factory/openQA/openQA.changes    2025-11-24 
14:11:27.707002883 +0100
+++ /work/SRC/openSUSE:Factory/.openQA.new.14147/openQA.changes 2025-12-01 
11:13:00.485711717 +0100
@@ -1,0 +2,25 @@
+Fri Nov 28 20:38:53 UTC 2025 - [email protected]
+
+- Update to version 5.1764349525.ffb59486:
+  * Also use TIMEOUT_SCALE for priority malus calculation
+  * docs: Fix wrapping and typo
+  * Document multi machine ovs flow setup and IPv6 usage
+  * Avoid computing time constraint for scheduled product cleanup in Perl
+  * rpm: Move `…-enqueue-needle-ref-cleanup` to other `…-enqueue-…` scripts
+  * Add task to limit scheduled products similar to audit events
+  * Extract generic parts from audit event cleanup task into generic task
+  * parser: ktap: Show full output by default if no line was parsed
+  * Ignore npm scripts also via `.npmrc` to make bare npm calls more secure
+  * Avoid repeating `MAIN_SETTINGS` in various places
+  * Fix possibly excessive memory use when computer test result overview
+  * Fix typo in `_prepare_complex_query_search_args`
+  * Fix indentation in `overview.html.ep`
+  * Prevent logging AMQP credentials in debug output
+  * Make restart_openqa_job emit proper event payload
+  * Enable gru tasks to emit AMQP messages
+  * Remove explicit loading AMQP plugin in Gru plugin
+  * Emit restart events when job restarted automatically
+  * Add debug message about priority malus
+  * Fix ordering of job groups after 2ad929ceca43d
+
+-------------------------------------------------------------------

Old:
----
  openQA-5.1763743683.1da97aa2.obscpio

New:
----
  openQA-5.1764349525.ffb59486.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ openQA-client-test.spec ++++++
--- /var/tmp/diff_new_pack.95quJ0/_old  2025-12-01 11:13:04.797894122 +0100
+++ /var/tmp/diff_new_pack.95quJ0/_new  2025-12-01 11:13:04.825895306 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-client
 Name:           %{short_name}-test
-Version:        5.1763743683.1da97aa2
+Version:        5.1764349525.ffb59486
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA-devel-test.spec ++++++
--- /var/tmp/diff_new_pack.95quJ0/_old  2025-12-01 11:13:05.097906812 +0100
+++ /var/tmp/diff_new_pack.95quJ0/_new  2025-12-01 11:13:05.105907150 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-devel
 Name:           %{short_name}-test
-Version:        5.1763743683.1da97aa2
+Version:        5.1764349525.ffb59486
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA-test.spec ++++++
--- /var/tmp/diff_new_pack.95quJ0/_old  2025-12-01 11:13:05.229912396 +0100
+++ /var/tmp/diff_new_pack.95quJ0/_new  2025-12-01 11:13:05.229912396 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA
 Name:           %{short_name}-test
-Version:        5.1763743683.1da97aa2
+Version:        5.1764349525.ffb59486
 Release:        0
 Summary:        Test package for openQA
 License:        GPL-2.0-or-later

++++++ openQA-worker-test.spec ++++++
--- /var/tmp/diff_new_pack.95quJ0/_old  2025-12-01 11:13:05.349917472 +0100
+++ /var/tmp/diff_new_pack.95quJ0/_new  2025-12-01 11:13:05.349917472 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-worker
 Name:           %{short_name}-test
-Version:        5.1763743683.1da97aa2
+Version:        5.1764349525.ffb59486
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA.spec ++++++
--- /var/tmp/diff_new_pack.95quJ0/_old  2025-12-01 11:13:05.481923056 +0100
+++ /var/tmp/diff_new_pack.95quJ0/_new  2025-12-01 11:13:05.485923225 +0100
@@ -17,7 +17,7 @@
 
 
 %define openqa_main_service openqa-webui.service
-%define openqa_extra_services openqa-gru.service openqa-websockets.service 
openqa-scheduler.service openqa-enqueue-audit-event-cleanup.service 
openqa-enqueue-audit-event-cleanup.timer openqa-enqueue-asset-cleanup.service 
openqa-enqueue-git-auto-update.service openqa-enqueue-asset-cleanup.timer 
openqa-enqueue-result-cleanup.service openqa-enqueue-result-cleanup.timer 
openqa-enqueue-bug-cleanup.service openqa-enqueue-bug-cleanup.timer 
openqa-enqueue-git-auto-update.timer openqa-enqueue-needle-ref-cleanup.service 
openqa-enqueue-needle-ref-cleanup.timer
+%define openqa_extra_services openqa-gru.service openqa-websockets.service 
openqa-scheduler.service openqa-enqueue-audit-event-cleanup.service 
openqa-enqueue-audit-event-cleanup.timer openqa-enqueue-asset-cleanup.service 
openqa-enqueue-git-auto-update.service openqa-enqueue-asset-cleanup.timer 
openqa-enqueue-result-cleanup.service openqa-enqueue-result-cleanup.timer 
openqa-enqueue-bug-cleanup.service openqa-enqueue-bug-cleanup.timer 
openqa-enqueue-git-auto-update.timer openqa-enqueue-needle-ref-cleanup.service 
openqa-enqueue-needle-ref-cleanup.timer 
openqa-enqueue-scheduled-product-cleanup.service 
openqa-enqueue-scheduled-product-cleanup.timer
 %define openqa_services %{openqa_main_service} %{openqa_extra_services}
 %define openqa_worker_services openqa-worker.target openqa-slirpvde.service 
openqa-vde_switch.service openqa-worker-cacheservice.service 
openqa-worker-cacheservice-minion.service
 %define openqa_localdb_services openqa-setup-db.service openqa-dump-db.service 
openqa-dump-db.timer
@@ -99,7 +99,7 @@
 %define devel_requires %devel_no_selenium_requires chromedriver
 
 Name:           openQA
-Version:        5.1763743683.1da97aa2
+Version:        5.1764349525.ffb59486
 Release:        0
 Summary:        The openQA web-frontend, scheduler and tools
 License:        GPL-2.0-or-later
@@ -633,6 +633,8 @@
 %{_unitdir}/openqa-enqueue-bug-cleanup.timer
 %{_unitdir}/openqa-enqueue-needle-ref-cleanup.service
 %{_unitdir}/openqa-enqueue-needle-ref-cleanup.timer
+%{_unitdir}/openqa-enqueue-scheduled-product-cleanup.service
+%{_unitdir}/openqa-enqueue-scheduled-product-cleanup.timer
 %{_tmpfilesdir}/openqa-webui.conf
 # web libs
 %dir %{_datadir}/openqa
@@ -664,13 +666,14 @@
 %{_datadir}/openqa/script/openqa-enqueue-audit-event-cleanup
 %{_datadir}/openqa/script/openqa-enqueue-bug-cleanup
 %{_datadir}/openqa/script/openqa-enqueue-git-auto-update
+%{_datadir}/openqa/script/openqa-enqueue-needle-ref-cleanup
 %{_datadir}/openqa/script/openqa-enqueue-result-cleanup
+%{_datadir}/openqa/script/openqa-enqueue-scheduled-product-cleanup
 %{_datadir}/openqa/script/openqa-gru
 %{_datadir}/openqa/script/openqa-rollback
 %{_datadir}/openqa/script/openqa-webui-daemon
 %{_datadir}/openqa/script/upgradedb
 %{_datadir}/openqa/script/modify_needle
-%{_datadir}/openqa/script/openqa-enqueue-needle-ref-cleanup
 # TODO: define final user
 %defattr(-,geekotest,root)
 # attention: never package subdirectories owned by a user other

++++++ openQA-5.1763743683.1da97aa2.obscpio -> 
openQA-5.1764349525.ffb59486.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/.npmrc 
new/openQA-5.1764349525.ffb59486/.npmrc
--- old/openQA-5.1763743683.1da97aa2/.npmrc     1970-01-01 01:00:00.000000000 
+0100
+++ new/openQA-5.1764349525.ffb59486/.npmrc     2025-11-28 18:05:25.000000000 
+0100
@@ -0,0 +1,2 @@
+# for the sake of security as we don't need scripts anyway
+ignore-scripts=true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/dist/rpm/openQA.spec 
new/openQA-5.1764349525.ffb59486/dist/rpm/openQA.spec
--- old/openQA-5.1763743683.1da97aa2/dist/rpm/openQA.spec       2025-11-21 
17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/dist/rpm/openQA.spec       2025-11-28 
18:05:25.000000000 +0100
@@ -17,7 +17,7 @@
 
 # can't use linebreaks here!
 %define openqa_main_service openqa-webui.service
-%define openqa_extra_services openqa-gru.service openqa-websockets.service 
openqa-scheduler.service openqa-enqueue-audit-event-cleanup.service 
openqa-enqueue-audit-event-cleanup.timer openqa-enqueue-asset-cleanup.service 
openqa-enqueue-git-auto-update.service openqa-enqueue-asset-cleanup.timer 
openqa-enqueue-result-cleanup.service openqa-enqueue-result-cleanup.timer 
openqa-enqueue-bug-cleanup.service openqa-enqueue-bug-cleanup.timer 
openqa-enqueue-git-auto-update.timer openqa-enqueue-needle-ref-cleanup.service 
openqa-enqueue-needle-ref-cleanup.timer
+%define openqa_extra_services openqa-gru.service openqa-websockets.service 
openqa-scheduler.service openqa-enqueue-audit-event-cleanup.service 
openqa-enqueue-audit-event-cleanup.timer openqa-enqueue-asset-cleanup.service 
openqa-enqueue-git-auto-update.service openqa-enqueue-asset-cleanup.timer 
openqa-enqueue-result-cleanup.service openqa-enqueue-result-cleanup.timer 
openqa-enqueue-bug-cleanup.service openqa-enqueue-bug-cleanup.timer 
openqa-enqueue-git-auto-update.timer openqa-enqueue-needle-ref-cleanup.service 
openqa-enqueue-needle-ref-cleanup.timer 
openqa-enqueue-scheduled-product-cleanup.service 
openqa-enqueue-scheduled-product-cleanup.timer
 %define openqa_services %{openqa_main_service} %{openqa_extra_services}
 %define openqa_worker_services openqa-worker.target openqa-slirpvde.service 
openqa-vde_switch.service openqa-worker-cacheservice.service 
openqa-worker-cacheservice-minion.service
 %define openqa_localdb_services openqa-setup-db.service openqa-dump-db.service 
openqa-dump-db.timer
@@ -633,6 +633,8 @@
 %{_unitdir}/openqa-enqueue-bug-cleanup.timer
 %{_unitdir}/openqa-enqueue-needle-ref-cleanup.service
 %{_unitdir}/openqa-enqueue-needle-ref-cleanup.timer
+%{_unitdir}/openqa-enqueue-scheduled-product-cleanup.service
+%{_unitdir}/openqa-enqueue-scheduled-product-cleanup.timer
 %{_tmpfilesdir}/openqa-webui.conf
 # web libs
 %dir %{_datadir}/openqa
@@ -664,13 +666,14 @@
 %{_datadir}/openqa/script/openqa-enqueue-audit-event-cleanup
 %{_datadir}/openqa/script/openqa-enqueue-bug-cleanup
 %{_datadir}/openqa/script/openqa-enqueue-git-auto-update
+%{_datadir}/openqa/script/openqa-enqueue-needle-ref-cleanup
 %{_datadir}/openqa/script/openqa-enqueue-result-cleanup
+%{_datadir}/openqa/script/openqa-enqueue-scheduled-product-cleanup
 %{_datadir}/openqa/script/openqa-gru
 %{_datadir}/openqa/script/openqa-rollback
 %{_datadir}/openqa/script/openqa-webui-daemon
 %{_datadir}/openqa/script/upgradedb
 %{_datadir}/openqa/script/modify_needle
-%{_datadir}/openqa/script/openqa-enqueue-needle-ref-cleanup
 # TODO: define final user
 %defattr(-,geekotest,root)
 # attention: never package subdirectories owned by a user other
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/docs/Networking.asciidoc 
new/openQA-5.1764349525.ffb59486/docs/Networking.asciidoc
--- old/openQA-5.1763743683.1da97aa2/docs/Networking.asciidoc   2025-11-21 
17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/docs/Networking.asciidoc   2025-11-28 
18:05:25.000000000 +0100
@@ -100,6 +100,15 @@
 The name of the bridge (default: `br1`) will be set in
 `/etc/sysconfig/os-autoinst-openvswitch`.
 
+A huge part of the magic happens in os-autoinst-openvswitch. On startup this
+daemon will create flow rules in the OVS bridge that allow the SUT VMs to talk
+to upstream networks despite being in a VLAN to isolate from other tests.
+These flow rules implement an address translation from the SUT IPs to
+intermediate IPs so that it doesn't matter if multiple tests are running at
+the same time with conflicting IP addresses. These intermediate IP addresses
+can then be NATed (again) to upstream networks by regular iptables/NFT
+masquerading rules.
+
 ===== Configure virtual interfaces
 
 The script will add the bridge device and the tap devices for every
@@ -112,6 +121,12 @@
 for further information.
 
 ===== Configure NAT with firewalld
+
+NOTE: `os-autoinst-setup-multi-machine` can set this up for you, but using
+plain NFT instead (which `os-autoinst-setup-multi-machine` can setup as well)
+or firewalld is recommended due to firewalld's suboptimal performance with
+many tap interfaces present.
+
 The required firewall rules for masquerading (NAT) and zone configuration
 for the trusted zone will be set up. The bridge devices will be added to
 the zone.
@@ -233,9 +248,16 @@
 
 ```
 ip link set dev eth0 up mtu 1380
+
+# for ipv4
 ip a add dev eth0 10.0.2.15/24
 ip r add default via 10.0.2.2
 echo 'nameserver 8.8.8.8' > /etc/resolv.conf
+
+# for ipv6
+ip a add dev eth0 fec0::15/64
+ip r add default via fec0::2
+echo 'nameserver 2606:4700:4700::1111' > /etc/resolv.conf
 ```
 
 The MTU is chosen in accordance with what the openSUSE test distribution uses
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/docs/UsersGuide.asciidoc 
new/openQA-5.1764349525.ffb59486/docs/UsersGuide.asciidoc
--- old/openQA-5.1763743683.1da97aa2/docs/UsersGuide.asciidoc   2025-11-21 
17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/docs/UsersGuide.asciidoc   2025-11-28 
18:05:25.000000000 +0100
@@ -1599,6 +1599,7 @@
 - limit_audit_events
 - limit_bugs
 - limit_results_and_logs
+- limit_scheduled_products
 - limit_screenshots
 
 These are no-ops if a task is already running so they can safely be enqueued
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/etc/openqa/openqa.ini 
new/openQA-5.1764349525.ffb59486/etc/openqa/openqa.ini
--- old/openQA-5.1763743683.1da97aa2/etc/openqa/openqa.ini      2025-11-21 
17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/etc/openqa/openqa.ini      2025-11-28 
18:05:25.000000000 +0100
@@ -333,6 +333,11 @@
 ## job priority. Can be 0 to disable and negative to prioritize long running 
job
 ## scenarios.
 #max_job_time_prio_scale = 100
+## Minimum storage duration for scheduled products
+## Scheduled products are kept as long as the jobs they have created exist. So 
their retention is controlled by the
+## retention of their jobs and you normally don't need to tweak this value. It 
only serves as a minimum retention
+## so scheduled products without any jobs (e.g. due to errors) are not 
immediately cleaned up.
+#scheduled_product_min_storage_duration = 34
 
 [archiving]
 ## Moves logs of jobs which are preserved during the cleanup because they are 
considered important
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Parser/Format/KTAP.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/Parser/Format/KTAP.pm
--- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Parser/Format/KTAP.pm   
2025-11-21 17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Parser/Format/KTAP.pm   
2025-11-28 18:05:25.000000000 +0100
@@ -48,7 +48,11 @@
     $self->test or return;
 
     my $line = $result->as_string;
-    return unless $line =~ /^#\s*(?<status>ok|not 
ok)\s+(?<index>\d+)\s+(?<name>[^#]*)/;
+    unless ($line =~ /^#\s*(?<status>ok|not 
ok)\s+(?<index>\d+)\s+(?<name>[^#]*)/) {
+        push @{$steps->{unparsed_lines}}, $line;
+        return;
+    }
+    $steps->{parsed_lines_count}++;
     return if $line =~ /#\s*SKIP\b/i;
     my ($status, $index, $subtest_name) = @+{qw(status index name)};
 
@@ -92,6 +96,22 @@
 
     $steps->{name} = $self->test->{name};
     $steps->{test} = $self->test;
+
+    # If there are no parsed lines, default to simply show the whole log.
+    # This is better than not showing anything, impeding the user to see
+    # the buttons of adding product or test bugs and requiring them to
+    # search and open individual log assets.
+    if (!$steps->{parsed_lines_count}) {
+        my $filename = "KTAP-@{[$steps->{name}]}.txt";
+        push @{$steps->{details}},
+          {
+            text => $filename,
+            title => $steps->{name},
+            result => 'ok',
+          };
+        $self->_add_output({file => $filename, content => join("\n", 
@{$steps->{unparsed_lines}})});
+    }
+
     
$self->generated_tests_results->add(OpenQA::Parser::Result::OpenQA->new($steps));
 
     $self->test(undef);
@@ -106,11 +126,11 @@
     while (my $result = $parser->next) {
         next if $result->type eq 'version' || $result->type eq 'plan';
 
-        if ($result->type eq 'comment' && $result->as_string =~ 
/^#\s*selftests:\s+/) {
-            $self->_testgroup_init($result->as_string);
-            next;
-        }
-        if ($result->type eq 'comment' && $result->as_string =~ /^#\s*(ok|not 
ok)\s+\d+\s+/) {
+        if ($result->type eq 'comment') {
+            if ($result->as_string =~ /^#\s*selftests:\s+/) {
+                $self->_testgroup_init($result->as_string);
+                next;
+            }
             $self->_parse_subtest($result);
             next;
         }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/Result/Jobs.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/Result/Jobs.pm
--- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/Result/Jobs.pm   
2025-11-21 17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/Result/Jobs.pm   
2025-11-28 18:05:25.000000000 +0100
@@ -418,7 +418,7 @@
             $settings->{$_->key} = $_->value;
         }
     }
-    for my $column (qw(DISTRI VERSION FLAVOR MACHINE ARCH BUILD TEST)) {
+    for my $column (MAIN_SETTINGS) {
         if (my $value = $self->$column) { $settings->{$column} = $value }
     }
     $settings->{NAME} = sprintf '%08d-%s', $self->id, $self->name;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/AuditEvents.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/AuditEvents.pm
--- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/AuditEvents.pm 
2025-11-21 17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/AuditEvents.pm 
2025-11-28 18:05:25.000000000 +0100
@@ -24,7 +24,7 @@
     needle => 'needle_%',
 );
 
-sub delete_entries_exceeding_storage_duration {
+sub delete_expired_entries {
     my ($self, %options) = @_;
 
     my @event_type_globs;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/JobGroupParents.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/JobGroupParents.pm
--- 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/JobGroupParents.pm 
    2025-11-21 17:48:03.000000000 +0100
+++ 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/JobGroupParents.pm 
    2025-11-28 18:05:25.000000000 +0100
@@ -10,7 +10,8 @@
 sub job_groups_and_parents {
     my $self = shift;
 
-    my $params = {order_by => [{-asc => 'me.sort_order'}, {-asc => 
'me.name'}], prefetch => 'children'};
+    my @order = qw(me.sort_order children.sort_order me.name children.name);
+    my $params = {order_by => \@order, prefetch => 'children'};
     my @parents = $self->search({}, $params)->all;
     my $schema = $self->result_source->schema;
     my @groups_without_parent = $schema->resultset('JobGroups')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/Jobs.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/Jobs.pm
--- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/Jobs.pm        
2025-11-21 17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/Jobs.pm        
2025-11-28 18:05:25.000000000 +0100
@@ -23,6 +23,7 @@
 use Mojolicious::Validator::Validation;
 use Time::HiRes 'time';
 use DateTime;
+use List::Util qw(any);
 use Scalar::Util qw(looks_like_number);
 
 =head2 latest_build
@@ -55,20 +56,16 @@
     $attrs{order_by} = {-desc => 'me.id'};    # More reliable for tests than 
t_created
     $attrs{columns} = qw(BUILD);
 
+    my $job_settings = $schema->resultset('JobSettings');
     foreach my $key (keys %args) {
+        my $key_uc = uc $key;
         my $value = $args{$key};
-
-        if (grep { $key eq $_ } qw(distri version flavor machine arch build 
test)) {
-            push(@conds, {'me.' . uc($key) => $value});
+        if (any { $key_uc eq $_ } OpenQA::Schema::Result::Jobs::MAIN_SETTINGS) 
{
+            push @conds, {'me.' . $key_uc => $value};
         }
         else {
-
-            my $subquery = $schema->resultset('JobSettings')->search(
-                {
-                    key => uc($key),
-                    value => $value
-                });
-            push(@conds, {'me.id' => {-in => 
$subquery->get_column('job_id')->as_query}});
+            my $subquery = $job_settings->search({key => $key_uc, value => 
$value});
+            push @conds, {'me.id' => {-in => 
$subquery->get_column('job_id')->as_query}};
         }
     }
 
@@ -97,16 +94,8 @@
     my @latest;
     my %seen;
     foreach my $job (@jobs) {
-        my $test = $job->TEST;
-        my $distri = $job->DISTRI;
-        my $version = $job->VERSION;
-        my $build = $job->BUILD;
-        my $flavor = $job->FLAVOR;
-        my $arch = $job->ARCH;
-        my $machine = $job->MACHINE // '';
-        my $key = "$distri-$version-$build-$test-$flavor-$arch-$machine";
-        next if $seen{$key}++;
-        push(@latest, $job);
+        my $key = join('-', map { $job->$_ // '' } 
OpenQA::Schema::Result::Jobs::MAIN_SETTINGS);
+        push @latest, $job unless $seen{$key}++;
     }
 
     return @latest;
@@ -157,12 +146,21 @@
     # assign scheduled product
     $new_job_args{scheduled_product_id} = $scheduled_product_id;
 
-    if (looks_like_number($settings{MAX_JOB_TIME}) and $settings{MAX_JOB_TIME} 
> DEFAULT_MAX_JOB_TIME) {
-        my $scale = 
OpenQA::App->singleton->config->{misc_limits}->{max_job_time_prio_scale};
-        $new_job_args{priority} += int($settings{MAX_JOB_TIME} / $scale) if 
$scale;
+    my $debug_msg;
+    my $max_job_time = looks_like_number $settings{MAX_JOB_TIME} ? 
$settings{MAX_JOB_TIME} : 0;
+    my $timeout_scale = looks_like_number $settings{TIMEOUT_SCALE} ? 
$settings{TIMEOUT_SCALE} : 0;
+    $max_job_time *= $timeout_scale if $max_job_time and $timeout_scale > 1;
+    if ($max_job_time and $max_job_time > DEFAULT_MAX_JOB_TIME) {
+        if (my $scale = 
OpenQA::App->singleton->config->{misc_limits}->{max_job_time_prio_scale}) {
+            my $malus = int($max_job_time / $scale);
+            $debug_msg = sprintf 'Adding priority malus to newly created job 
(old: %d, malus: %s)',
+              $new_job_args{priority}, $malus;
+            $new_job_args{priority} += $malus;
+        }
     }
 
     my $job = $self->create(\%new_job_args);
+    log_debug(sprintf "(Job %d) $debug_msg", $job->id) if $debug_msg;
 
     # add job settings
     my @job_settings;
@@ -315,7 +313,7 @@
     else {
         my %js_settings;
         # Check if the settings are between the arguments passed via query url
-        # they come in lowercase, so mace sure $key is lc'ed
+        # they come in lowercase, so make sure $key is lc'ed
         for my $key (qw(ISO HDD_1 WORKER_CLASS)) {
             $js_settings{$key} = $args->{lc $key} if defined $args->{lc $key};
         }
@@ -347,16 +345,53 @@
     return (\@conds, \%attrs);
 }
 
-sub complex_query ($self, %args) {
-    # For args where we accept a list of values, allow passing either an
-    # array ref or a comma-separated list
+sub _accept_comma_separated_arg_values ($args) {
     for my $arg (qw(state ids result modules modules_result)) {
-        next unless $args{$arg};
-        $args{$arg} = [split(',', $args{$arg})] unless (ref($args{$arg}) eq 
'ARRAY');
+        next unless my $value = $args->{$arg};
+        $args->{$arg} = [split ',', $value] unless ref $value eq 'ARRAY';
     }
+}
+
+sub complex_query ($self, %args) {
+    _accept_comma_separated_arg_values(\%args);
     my ($conds, $attrs) = $self->_prepare_complex_query_search_args(\%args);
-    my $jobs = $self->search({-and => $conds}, $attrs);
-    return $jobs;
+    return $self->search({-and => $conds}, $attrs);
+}
+
+sub complex_query_latest_ids ($self, %args) {
+    # prepare basic search conditions and attributes
+    _accept_comma_separated_arg_values(\%args);
+    my ($conds, $attrs) = $self->_prepare_complex_query_search_args(\%args);
+    my $filters = $args{filters};
+    my $has_filters = $filters && @$filters > 0;
+    my $rows = $has_filters ? delete $attrs->{rows} : undef;  # when 
filtering, limit rows only in outer/filtering query
+    if (my $until = $args{until}) { push @$conds, {'me.t_created' => {'<=' => 
$until}} }
+
+    # set attributes to return only the latest job IDs for a certain 
combination of TEST, DISTRI, VERSION, …
+    $attrs->{order_by} = \['max(me.id) DESC'];
+    $attrs->{select} = ['max(me.id)'];
+    $attrs->{as} = ['id'];
+    $attrs->{group_by} = [OpenQA::Schema::Result::Jobs::MAIN_SETTINGS];
+
+    # execute the search; use a sub query if filtering is enabled
+    my $search = $self->search({-and => $conds}, $attrs);
+    if ($has_filters) {
+        # add another layer of querying for filters
+        # note: The filtering cannot be applied in the same query we do the 
grouping to return only the latest job IDs.
+        #       Otherwise adding filter parameters would lead to old jobs 
showing up. That is not wanted (and we have
+        #       therefore the test "filtering does not reveal old jobs" in 
`10-tests_overview.t` to test this).
+        my %filter_attrs = %$attrs;
+        $filter_attrs{rows} = $rows;
+        push @$filters, {'me.id' => {-in => $search->as_query}};
+        $search = $self->search({-and => $filters}, \%filter_attrs);
+    }
+    return [map { $_->id } $search->all];
+}
+
+sub latest_jobs_from_ids ($self, $latest_job_ids, $limit_from_initial_search) {
+    my %search_args = (id => {-in => $latest_job_ids});
+    my %options = (order_by => {-desc => 'id'}, rows => 
$limit_from_initial_search - 1);
+    return $self->search(\%search_args, \%options);
 }
 
 sub cancel_by_settings (
@@ -374,7 +409,7 @@
     my %precond = %{$settings};
     my %cond;
 
-    for my $key (qw(DISTRI VERSION FLAVOR MACHINE ARCH BUILD TEST)) {
+    for my $key (OpenQA::Schema::Result::Jobs::MAIN_SETTINGS) {
         $cond{$key} = delete $precond{$key} if defined $precond{$key};
     }
     if (keys %precond) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/ScheduledProducts.pm
 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/ScheduledProducts.pm
--- 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/ScheduledProducts.pm
   2025-11-21 17:48:03.000000000 +0100
+++ 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/ScheduledProducts.pm
   2025-11-28 18:05:25.000000000 +0100
@@ -118,4 +118,18 @@
     return $sth->fetchall_hashref([qw(latest_job_state latest_job_result)]);
 }
 
+sub delete_expired_entries ($self) {
+    # delete all scheduled products without jobs that are older than 
"scheduled_product_min_storage_duration"
+    my $min_storage_duration = 
OpenQA::App->singleton->config->{misc_limits}->{scheduled_product_min_storage_duration};
+    my $sth = $self->result_source->schema->storage->dbh->prepare(
+        <<~'END_SQL'
+        DELETE FROM scheduled_products
+            WHERE
+                (t_created < current_date - ?::interval)
+                AND (SELECT count(id) FROM jobs WHERE 
jobs.scheduled_product_id = scheduled_products.id LIMIT 1) = 0
+        END_SQL
+    );
+    $sth->execute("$min_storage_duration days");
+}
+
 1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Setup.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/Setup.pm
--- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Setup.pm        2025-11-21 
17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Setup.pm        2025-11-28 
18:05:25.000000000 +0100
@@ -240,6 +240,7 @@
             worker_limit_retry_delay => ONE_HOUR / 4,
             mcp_max_result_size => 500000,
             max_job_time_prio_scale => 100,
+            scheduled_product_min_storage_duration => 34,
         },
         archiving => {
             archive_preserved_important_jobs => 0,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Shared/Plugin/Gru.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/Shared/Plugin/Gru.pm
--- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Shared/Plugin/Gru.pm    
2025-11-21 17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Shared/Plugin/Gru.pm    
2025-11-28 18:05:25.000000000 +0100
@@ -44,6 +44,7 @@
       OpenQA::Task::Job::Restart
       OpenQA::Task::Iso::Schedule
       OpenQA::Task::Bug::Limit
+      OpenQA::Task::ScheduledProduct::Limit
     );
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/AuditEvents/Limit.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/AuditEvents/Limit.pm
--- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/AuditEvents/Limit.pm       
2025-11-21 17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/AuditEvents/Limit.pm       
2025-11-28 18:05:25.000000000 +0100
@@ -1,28 +1,10 @@
-# Copyright 2019-2021 SUSE LLC
+# Copyright SUSE LLC
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 package OpenQA::Task::AuditEvents::Limit;
-use Mojo::Base 'Mojolicious::Plugin';
-use OpenQA::Task::Utils qw(acquire_limit_lock_or_retry);
-use OpenQA::Task::SignalGuard;
-use Time::Seconds;
+use Mojo::Base 'OpenQA::Task::Table::Limit';
 
-sub register {
-    my ($self, $app) = @_;
-    $app->minion->add_task(limit_audit_events => sub { _limit($app, @_) });
-}
-
-sub _limit {
-    my ($app, $job) = @_;
-    my $ensure_task_retry_on_termination_signal_guard = 
OpenQA::Task::SignalGuard->new($job);
-
-    # prevent multiple limit_audit_events tasks to run in parallel
-    return $job->finish('Previous limit_audit_events job is still active')
-      unless my $guard = $app->minion->guard('limit_audit_events_task', 
ONE_DAY);
-
-    return undef unless my $limit_guard = acquire_limit_lock_or_retry($job);
-
-    
$app->schema->resultset('AuditEvents')->delete_entries_exceeding_storage_duration;
-}
+has task_name => 'limit_audit_events';
+has table => 'AuditEvents';
 
 1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/Job/Restart.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/Job/Restart.pm
--- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/Job/Restart.pm     
2025-11-21 17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/Job/Restart.pm     
2025-11-28 18:05:25.000000000 +0100
@@ -5,6 +5,7 @@
 use Mojo::Base 'Mojolicious::Plugin', -signatures;
 
 use Time::Seconds;
+use OpenQA::Events;
 
 sub register ($self, $app, @args) {
     $app->minion->add_task(restart_job => \&_restart_job);
@@ -17,6 +18,11 @@
 sub restart_openqa_job ($minion_job, $openqa_job) {
     my $cloned_job_or_error = $openqa_job->auto_duplicate;
     my $is_ok = ref $cloned_job_or_error || $cloned_job_or_error =~ 
qr/(already.*clone|direct parent)/i;
+    if (ref $cloned_job_or_error) {
+        my %event_data = (id => $openqa_job->id, result => 
$cloned_job_or_error->{cluster_cloned}, auto => 1);
+        OpenQA::Events->singleton->emit_event('openqa_job_restart', data => 
\%event_data);
+    }
+
     $minion_job->note(
         ref $cloned_job_or_error
         ? (cluster_cloned => $cloned_job_or_error->{cluster_cloned})
@@ -33,8 +39,11 @@
     my $openqa_job = $app->schema->resultset('Jobs')->find($openqa_job_id);
     return $minion_job->finish("Job $openqa_job_id does not exist.") unless 
$openqa_job;
 
+    _init_amqp_plugin($app);
     # duplicate job and finish normally if no error was returned or job can 
not be cloned
     my ($is_ok, $cloned_job_or_error) = restart_openqa_job($minion_job, 
$openqa_job);
+    _wait_for_event_publish($app);
+
     return $minion_job->finish(ref $cloned_job_or_error ? undef : 
$cloned_job_or_error) if $is_ok;
 
     # retry a certain number of times, maybe the transaction failed due to a 
conflict
@@ -46,4 +55,16 @@
     $minion_job->retry({delay => restart_delay});
 }
 
+sub _init_amqp_plugin ($app) {
+    return undef unless $app->config->{amqp}->{enabled};
+    $app->plugin('AMQP');    # Needs to be loaded again from forked process
+    Mojo::IOLoop->singleton->one_tick;
+}
+
+sub _wait_for_event_publish ($app) {
+    return undef unless $app->config->{amqp}->{enabled};
+    OpenQA::Events->singleton->once(amqp_handled => sub { Mojo::IOLoop->stop 
});
+    Mojo::IOLoop->start;
+}
+
 1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/ScheduledProduct/Limit.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/ScheduledProduct/Limit.pm
--- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/ScheduledProduct/Limit.pm  
1970-01-01 01:00:00.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/ScheduledProduct/Limit.pm  
2025-11-28 18:05:25.000000000 +0100
@@ -0,0 +1,10 @@
+# Copyright SUSE LLC
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+package OpenQA::Task::ScheduledProduct::Limit;
+use Mojo::Base 'OpenQA::Task::Table::Limit';
+
+has task_name => 'limit_scheduled_products';
+has table => 'ScheduledProducts';
+
+1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/Table/Limit.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/Table/Limit.pm
--- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/Table/Limit.pm     
1970-01-01 01:00:00.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/Table/Limit.pm     
2025-11-28 18:05:25.000000000 +0100
@@ -0,0 +1,29 @@
+# Copyright SUSE LLC
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+package OpenQA::Task::Table::Limit;
+use Mojo::Base 'Mojolicious::Plugin', -signatures;
+use OpenQA::Task::Utils qw(acquire_limit_lock_or_retry);
+use OpenQA::Task::SignalGuard;
+use Time::Seconds;
+
+has 'task_name';
+has 'table';
+
+sub register ($self, $app, @) {
+    $app->minion->add_task($self->task_name => sub { $self->_limit($app, @_) 
});
+}
+
+sub _limit ($self, $app, $job, @) {
+    my $ensure_task_retry_on_termination_signal_guard = 
OpenQA::Task::SignalGuard->new($job);
+
+    # prevent multiple limit tasks to run in parallel
+    my $task_name = $self->task_name;
+    my $guard = $app->minion->guard("${task_name}_task", ONE_DAY);
+    return $job->finish("Previous $task_name job is still active") unless 
$guard;
+    return undef unless my $limit_guard = acquire_limit_lock_or_retry($job);
+
+    $app->schema->resultset($self->table)->delete_expired_entries;
+}
+
+1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm
--- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm 
2025-11-21 17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm 
2025-11-28 18:05:25.000000000 +0100
@@ -226,29 +226,15 @@
 sub overview ($self) {
     my ($search_args, $groups) = $self->compose_job_overview_search_args;
     my $failed_modules = $self->param_hash('failed_modules');
-    my $states = $self->param_hash('state');
-    my $results = $self->param_hash('result');
-    my $archs = $self->param_hash('arch');
-    my $machines = $self->param_hash('machine');
-
-    my @jobs = 
$self->schema->resultset('Jobs')->complex_query(%$search_args)->latest_jobs;
+    my $jobs_rs = $self->schema->resultset('Jobs');
+    my $latest_job_ids = $jobs_rs->complex_query_latest_ids(%$search_args);
+    my @jobs = $jobs_rs->latest_jobs_from_ids($latest_job_ids, 
$search_args->{limit})->all;
 
     my @job_hashes;
     for my $job (@jobs) {
-        next if $states && !$states->{$job->state};
-        next if $results && !$results->{$job->result};
-        next if $archs && !$archs->{$job->ARCH};
-        next if $machines && !$machines->{$job->MACHINE};
-        if ($failed_modules) {
-            next if $job->result ne OpenQA::Jobs::Constants::FAILED;
-            next unless 
OpenQA::Utils::any_array_item_contained_by_hash($job->failed_modules, 
$failed_modules);
-        }
-        push(
-            @job_hashes,
-            {
-                id => $job->id,
-                name => $job->name,
-            });
+        next
+          if $failed_modules && 
!OpenQA::Utils::any_array_item_contained_by_hash($job->failed_modules, 
$failed_modules);
+        push @job_hashes, {id => $job->id, name => $job->name};
     }
     $self->render(json => \@job_hashes);
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Controller/Test.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Controller/Test.pm
--- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Controller/Test.pm       
2025-11-21 17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Controller/Test.pm       
2025-11-28 18:05:25.000000000 +0100
@@ -685,10 +685,9 @@
 }
 
 # Take an job objects arrayref and prepare data structures for 'overview'
-sub _prepare_job_results ($self, $all_jobs, $limit) {
+sub _prepare_job_results ($self, $jobs, $job_ids) {
     my %archs;
     my %results;
-    my @job_ids;
     my $aggregated = {
         none => 0,
         passed => 0,
@@ -699,25 +698,8 @@
         running => 0,
         unknown => 0
     };
-    my $preferred_machines = _calculate_preferred_machines($all_jobs);
-
-    # read parameter for additional filtering
-    my $failed_modules = $self->param_hash('failed_modules');
-    my $states = $self->param_hash('state');
-    my $results = $self->param_hash('result');
-    my $archs = $self->param_hash('arch');
-    my $machines = $self->param_hash('machine');
-
-    my @jobs = grep {
-              (not $states or $states->{$_->state})
-          and (not $results or $results->{$_->result})
-          and (not $archs or $archs->{$_->ARCH})
-          and (not $machines or $machines->{$_->MACHINE})
-          and (not $failed_modules or $_->result eq 
OpenQA::Jobs::Constants::FAILED)
-    } @$all_jobs;
-    my $limit_exceeded = @jobs >= $limit;
-    @jobs = @jobs[0 .. ($limit - 1)] if $limit_exceeded;
-    my @jobids = map { $_->id } @jobs;
+    my @jobs = $jobs->all;
+    my $preferred_machines = _calculate_preferred_machines(\@jobs);
 
     # prefetch the number of available labels for those jobs
     my $schema = $self->schema;
@@ -726,7 +708,7 @@
     # prefetch test suite names from job settings
     my $job_settings
       = $schema->resultset('JobSettings')
-      ->search({job_id => {-in => [map { $_->id } @jobs]}, key => {-in => 
[qw(JOB_DESCRIPTION TEST_SUITE_NAME)]}});
+      ->search({job_id => {-in => $job_ids}, key => {-in => 
[qw(JOB_DESCRIPTION TEST_SUITE_NAME)]}});
     my %settings_by_job_id;
     for my $js ($job_settings->all) {
         $settings_by_job_id{$js->job_id}->{$js->key} = $js->value;
@@ -740,12 +722,13 @@
       = $schema->resultset('TestSuites')->search({name => \%desc_args}, 
{columns => [qw(name description)]});
     my %descriptions = map { $_->name => $_->description } @descriptions;
 
-    my $failed_modules_by_job = $self->_fetch_failed_modules_by_jobs(\@jobids);
-    my ($children_by_job, $parents_by_job) = 
$self->_fetch_dependencies_by_jobs(\@jobids);
+    my $failed_modules_by_job = $self->_fetch_failed_modules_by_jobs($job_ids);
+    my ($children_by_job, $parents_by_job) = 
$self->_fetch_dependencies_by_jobs($job_ids);
     foreach my $job (@jobs) {
         my $id = $job->id;
         my $result = $job->overview_result(
-            $comment_data, $aggregated, $failed_modules,
+            $comment_data, $aggregated,
+            $self->param_hash('failed_modules'),
             $failed_modules_by_job->{$id} || [],
             $self->param('todo')) or next;
         my $test = $job->TEST;
@@ -780,14 +763,11 @@
         $results{$distri}{$version}{$flavor}{$test} //= {};
         $results{$distri}{$version}{$flavor}{$test}{$arch} = $result;
 
-        # populate job IDs for adding multiple comments
-        push @job_ids, $id;
-
         # add description
         my $description = $settings_by_job_id{$id}->{JOB_DESCRIPTION} // 
$descriptions{$test_suite_names{$id}};
         $results{$distri}{$version}{$flavor}{$test}{description} //= 
$description;
     }
-    return ($limit_exceeded, \%archs, \%results, \@job_ids, $aggregated);
+    return (\%archs, \%results, $aggregated);
 }
 
 sub _prepare_groupids ($self) {
@@ -857,32 +837,26 @@
     my $validation = $self->validation;
     $validation->optional('t')->datetime;
     my $until = $validation->param('t');
-    my %stash = (
-        # build, version, distri are not mandatory and therefore not
-        # necessarily come from the search args so they can be undefined.
-        build => ref $search_args->{build} eq 'ARRAY' ? join(',', 
@{$search_args->{build}}) : $search_args->{build},
-        version => $search_args->{version},
-        distri => $search_args->{distri},
-        groups => $groups,
-        until => $until,
-        parallel_children_collapsable_results_sel => 
$config->{global}->{parallel_children_collapsable_results_sel},
-    );
-    my @jobs = 
$self->schema->resultset('Jobs')->complex_query(%$search_args)->latest_jobs($until);
-
-    my $limit = $config->{misc_limits}->{tests_overview_max_jobs};
-    (my $limit_exceeded, $stash{archs}, $stash{results}, $stash{job_ids}, 
$stash{aggregated})
-      = $self->_prepare_job_results(\@jobs, $limit);
+    $search_args->{until} = $until;
+    my $distri = $search_args->{distri};
+    my $version = $search_args->{version};
+    my $jobs_rs = $self->schema->resultset('Jobs');
+    my $latest_job_ids = $jobs_rs->complex_query_latest_ids(%$search_args);
+    my $limit = $search_args->{limit};    # one more than actual limit so we 
can check whether the limit was exceeded
+    my $exceeded_limit = @$latest_job_ids >= $limit ? $limit - 1 : 0;
+    my $jobs = $jobs_rs->latest_jobs_from_ids($latest_job_ids, $limit);
+    my ($archs, $results, $aggregated) = $self->_prepare_job_results($jobs, 
$latest_job_ids);
 
     # determine distri/version from job results if not explicitly specified 
via search args
-    my @distris = keys %{$stash{results}};
+    my @distris = keys %$results;
     my $formatted_distri;
     my $formatted_version;
     my $only_distri = scalar @distris == 1;
-    if (!defined $stash{distri} && $only_distri) {
+    if (!defined $distri && $only_distri) {
         $formatted_distri = $distris[0];
-        if (!defined $stash{version}) {
-            my @versions = keys %{$stash{results}->{$formatted_distri}};
-            $formatted_version = $versions[0] if (scalar @versions == 1);
+        if (!defined $version) {
+            my @versions = keys %{$results->{$formatted_distri}};
+            $formatted_version = $versions[0] if scalar @versions == 1;
         }
     }
 
@@ -899,18 +873,28 @@
         _add_distri_and_version_to_summary(\@summary_parts, $formatted_distri, 
$formatted_version, 1);
 
         # add distri and version from query parameters as regular strings
-        _add_distri_and_version_to_summary(\@summary_parts, $stash{distri}, 
$stash{version}, 0);
+        _add_distri_and_version_to_summary(\@summary_parts, $distri, $version, 
0);
     }
 
-    $self->stash(
-        %stash,
+    my %stash = (
+        # build, version, distri are not mandatory and therefore not
+        # necessarily come from the search args so they can be undefined.
+        build => ref $search_args->{build} eq 'ARRAY' ? join(',', 
@{$search_args->{build}}) : $search_args->{build},
+        distri => $distri,
+        version => $version,
+        groups => $groups,
+        archs => $archs,
+        results => $results,
+        aggregated => $aggregated,
+        job_ids => $latest_job_ids,
+        until => $until,
+        parallel_children_collapsable_results_sel => 
$config->{global}->{parallel_children_collapsable_results_sel},
         summary_parts => \@summary_parts,
         only_distri => $only_distri,
-        limit_exceeded => $limit_exceeded ? $limit : undef
+        limit_exceeded => $exceeded_limit,
     );
-    $self->respond_to(
-        json => {json => \%stash},
-        html => {template => 'test/overview'});
+    $self->stash(\%stash);
+    $self->respond_to(json => {json => \%stash}, html => {template => 
'test/overview'});
 }
 
 sub _get_latest_job ($self) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Plugin/AMQP.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Plugin/AMQP.pm
--- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Plugin/AMQP.pm   
2025-11-21 17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Plugin/AMQP.pm   
2025-11-28 18:05:25.000000000 +0100
@@ -28,6 +28,8 @@
 sub register ($self, $app, @args) {
     $self->{app} = $app;
     $self->{config} = $app->config;
+    my $config = $self->{config}->{amqp};
+    $config->{enabled} = 1;    # Needed for reloading the plugin later in the 
forked process
     Mojo::IOLoop->singleton->next_tick(
         sub {
             # register for events
@@ -56,30 +58,39 @@
     # create publisher and keep reference to avoid early destruction
     log_debug("Sending AMQP event: $topic");
     my $config = $self->{config}->{amqp};
-    my $url = Mojo::URL->new($config->{url});
-    $url->query({exchange => $config->{exchange}});
+    my $unsanitized_url = Mojo::URL->new($config->{url});
+    $unsanitized_url->query({exchange => $config->{exchange}});
     # append optional parameters
-    $url->query([cacertfile => $config->{cacertfile}]) if 
($config->{cacertfile});
-    $url->query([certfile => $config->{certfile}]) if ($config->{certfile});
-    $url->query([keyfile => $config->{keyfile}]) if ($config->{keyfile});
-    $url = $url->to_unsafe_string;
-    my $publisher = Mojo::RabbitMQ::Client::Publisher->new(url => $url);
+    $unsanitized_url->query([cacertfile => $config->{cacertfile}]) if 
($config->{cacertfile});
+    $unsanitized_url->query([certfile => $config->{certfile}]) if 
($config->{certfile});
+    $unsanitized_url->query([keyfile => $config->{keyfile}]) if 
($config->{keyfile});
+    my $url = $unsanitized_url->clone;
+    $unsanitized_url = $unsanitized_url->to_unsafe_string;
+    my $publisher = Mojo::RabbitMQ::Client::Publisher->new(url => 
$unsanitized_url);
     log_debug("AMQP URL: $url");
 
     $remaining_attempts //= $config->{publish_attempts};
     $retry_delay //= $config->{publish_retry_delay};
-    $publisher->publish_p($event_data, $headers, routing_key => 
$topic)->then(sub { log_debug "$topic published" })
-      ->catch(
+    $publisher->publish_p($event_data, $headers, routing_key => $topic)->then(
+        sub {
+            log_debug "$topic published";
+            OpenQA::Events->singleton->emit('amqp_handled');
+        }
+    )->catch(
         sub ($error) {
             my $left = looks_like_number $remaining_attempts && 
$remaining_attempts > 1 ? $remaining_attempts - 1 : 0;
             my $delay = $retry_delay * $config->{publish_retry_delay_factor};
             my ($event_id, $job_id) = ($event_data->{id} // 'none', 
$event_data->{job_id});
             my $additional_info = $job_id ? ", job ID: $job_id" : '';
             my $log_msg = "Publishing $topic failed: $error (event ID: 
$event_id$additional_info, $left attempts left)";
-            return log_error $log_msg unless $left;
             my $retry_function = sub ($loop) { $self->publish_amqp($topic, 
$event_data, $headers, $left, $delay) };
-            log_info $log_msg;
-            Mojo::IOLoop->timer($retry_delay => $retry_function) if $left;
+            if ($left) {
+                log_info $log_msg;
+                Mojo::IOLoop->timer($retry_delay => $retry_function);
+                return;
+            }
+            OpenQA::Events->singleton->emit('amqp_handled');
+            return log_error $log_msg;
         })->finally(sub { undef $publisher });
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Plugin/Helpers.pm 
new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Plugin/Helpers.pm
--- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Plugin/Helpers.pm        
2025-11-21 17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Plugin/Helpers.pm        
2025-11-28 18:05:25.000000000 +0100
@@ -11,7 +11,7 @@
 use OpenQA::Events;
 use OpenQA::Jobs::Constants qw(EXECUTION_STATES PRE_EXECUTION_STATES 
ABORTED_RESULTS FAILED NOT_COMPLETE_RESULTS);
 use Text::Glob qw(glob_to_regex_string);
-use List::Util qw(any);
+use List::Util qw(any min);
 use Feature::Compat::Try;
 
 sub register ($self, $app, $config) {
@@ -338,6 +338,9 @@
     $app->helper('reply.validation_error' => \&_validation_error);
 
     $app->helper(compose_job_overview_search_args => 
\&_compose_job_overview_search_args);
+    $app->helper(every_non_empty_param => \&_every_non_empty_param);
+    $app->helper(compute_overview_filtering_params => 
\&_compute_overview_filtering_params);
+
     $app->helper(groups_for_globs => \&_groups_for_globs);
     $app->helper(param_hash => \&_param_hash);
     $app->helper(
@@ -407,10 +410,9 @@
     $v->optional('groupid')->num(0, undef);
     $v->optional('modules', 'comma_separated');
     $v->optional('flavor', 'comma_separated');
-    $v->optional('limit', 'not_empty')->num(0, undef);
+    $v->optional('limit', 'not_empty')->num(1, undef);
 
     # add simple query params to search args
-    $search_args{limit} = $v->param('limit') if $v->is_valid('limit');
     for my $arg (qw(distri version flavor test)) {
         next unless $v->is_valid($arg);
         my @params = @{$v->every_param($arg) // []};
@@ -480,6 +482,10 @@
         }
     }
 
+    # limit results to return one row more than the configured/specified limit 
so we know when the limit is exceeded
+    my $configured_limit = 
$c->app->config->{misc_limits}->{tests_overview_max_jobs};
+    $search_args{limit} = ($v->is_valid('limit') ? min($configured_limit, 
$v->param('limit')) : $configured_limit) + 1;
+
     # exclude jobs which are already cloned by setting scope for 
OpenQA::Jobs::complex_query()
     $search_args{scope} = 'current';
 
@@ -495,9 +501,31 @@
     # allow filtering by comment text
     if (my $c = $v->param('comment')) { $search_args{comment_text} = $c }
 
+    # allow filtering by several job settings
+    $search_args{filters} = $c->compute_overview_filtering_params;
+
     return (\%search_args, \@groups);
 }
 
+sub _every_non_empty_param ($c, $param_key) {
+    [map { split ',', $_ } @{$c->every_param($param_key)}]
+}
+
+sub _compute_overview_filtering_params ($c) {
+    my $states = $c->every_non_empty_param('state');
+    my $results = $c->every_non_empty_param('result');
+    my $archs = $c->every_non_empty_param('arch');
+    my $machines = $c->every_non_empty_param('machine');
+    my $failed_modules = $c->every_non_empty_param('failed_modules');
+    my %filters;
+    $filters{state} = {-in => $states} if @$states;
+    $filters{result} = {-in => $results} if @$results;
+    $filters{result} = FAILED if @$failed_modules;
+    $filters{ARCH} = {-in => $archs} if @$archs;
+    $filters{MACHINE} = {-in => $machines} if @$machines;
+    return [\%filters];
+}
+
 sub _groups_for_globs ($c) {
     my $v = $c->validation;
     $v->optional($_, 'not_empty') for qw(group_glob not_group_glob);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/script/openqa-enqueue-scheduled-product-cleanup
 
new/openQA-5.1764349525.ffb59486/script/openqa-enqueue-scheduled-product-cleanup
--- 
old/openQA-5.1763743683.1da97aa2/script/openqa-enqueue-scheduled-product-cleanup
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/openQA-5.1764349525.ffb59486/script/openqa-enqueue-scheduled-product-cleanup
    2025-11-28 18:05:25.000000000 +0100
@@ -0,0 +1,3 @@
+#!/bin/sh -e
+[ "$1" = "-h" ] || [ "$1" = "--help" ] && echo "Enqueue openQA scheduled 
product cleanup" && exit
+exec "$(dirname "$0")"/openqa minion -m production job -e 
limit_scheduled_products "$@"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/systemd/openqa-enqueue-scheduled-product-cleanup.service
 
new/openQA-5.1764349525.ffb59486/systemd/openqa-enqueue-scheduled-product-cleanup.service
--- 
old/openQA-5.1763743683.1da97aa2/systemd/openqa-enqueue-scheduled-product-cleanup.service
   1970-01-01 01:00:00.000000000 +0100
+++ 
new/openQA-5.1764349525.ffb59486/systemd/openqa-enqueue-scheduled-product-cleanup.service
   2025-11-28 18:05:25.000000000 +0100
@@ -0,0 +1,9 @@
+[Unit]
+Description=openQA enqueue database scheduled product cleanup task
+After=postgresql.service openqa-setup-db.service
+Wants=openqa-setup-db.service
+
+[Service]
+Type=oneshot
+User=geekotest
+ExecStart=/usr/share/openqa/script/openqa-enqueue-scheduled-product-cleanup
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/systemd/openqa-enqueue-scheduled-product-cleanup.timer
 
new/openQA-5.1764349525.ffb59486/systemd/openqa-enqueue-scheduled-product-cleanup.timer
--- 
old/openQA-5.1763743683.1da97aa2/systemd/openqa-enqueue-scheduled-product-cleanup.timer
     1970-01-01 01:00:00.000000000 +0100
+++ 
new/openQA-5.1764349525.ffb59486/systemd/openqa-enqueue-scheduled-product-cleanup.timer
     2025-11-28 18:05:25.000000000 +0100
@@ -0,0 +1,9 @@
+[Unit]
+Description=Enqueues a scheduled product cleanup task for the openQA database 
every day.
+
+[Timer]
+OnCalendar=daily
+Persistent=true
+
+[Install]
+WantedBy=timers.target
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/t/10-jobs.t 
new/openQA-5.1764349525.ffb59486/t/10-jobs.t
--- old/openQA-5.1763743683.1da97aa2/t/10-jobs.t        2025-11-21 
17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/t/10-jobs.t        2025-11-28 
18:05:25.000000000 +0100
@@ -19,10 +19,11 @@
 use OpenQA::Test::Case;
 use Test::MockModule 'strict';
 use Test::Mojo;
+use Test::Output;
 use Test::Warnings qw(:report_warnings warning);
-use Mojo::File 'path';
+use Mojo::File qw(path tempdir);
 use Mojo::JSON qw(decode_json encode_json);
-use OpenQA::Test::Utils qw(perform_minion_jobs);
+use OpenQA::Test::Utils qw(perform_minion_jobs mock_io_loop);
 use OpenQA::Test::TimeLimit '30';
 
 binmode(STDOUT, ':encoding(UTF-8)');
@@ -80,13 +81,17 @@
     is $jobs->latest_build(version => 'Factory', distri => 'opensuse'), 
'0048@0815', 'latest for non-integer build';
     is $jobs->latest_build(version => '13.1', distri => 'opensuse'), '0091', 
'latest for different version differs';
 
-    my @latest = $jobs->latest_jobs;
-    my @ids = map { $_->id } @latest;
+    my $ids = $jobs->complex_query_latest_ids;
     # These two jobs have later clones in the fixture set, so should not appear
-    ok(grep(!/^(99962|99945)$/, @ids), 'jobs with later clones do not show up 
in latest jobs');
+    ok grep(!/^(99962|99945)$/, @$ids), 'jobs with later clones do not show up 
in latest jobs';
     # These are the later clones, they should appear
-    ok(grep(/^99963$/, @ids), 'cloned jobs appear as latest job');
-    ok(grep(/^99946$/, @ids), 'cloned jobs appear as latest job (2nd)');
+    ok grep(/^99963$/, @$ids), 'cloned jobs appear as latest job';
+    ok grep(/^99946$/, @$ids), 'cloned jobs appear as latest job (2nd)';
+
+    my $filtered_ids = $jobs->complex_query_latest_ids(filters => [{result => 
{-in => [FAILED, PASSED]}}]);
+    ok grep(!/^(99962|99945)$/, @$filtered_ids), 'jobs with later clones still 
not present';
+    ok grep(/^99946$/, @$filtered_ids), 'failed/passed job still returned';
+    ok grep(!/^99963$/, @$filtered_ids), 'running job filtered out';
 };
 
 
@@ -899,7 +904,8 @@
     my $finalize_job_count_before = @{$get_jobs->('finalize_job_results')};
     $job->update({state => SCHEDULED, result => NONE});
     $job->done(result => FAILED);
-    perform_minion_jobs($minion);
+    stdout_like { perform_minion_jobs($minion) } qr/Job \d+ duplicated as \d+/,
+      'check debug message from auto_duplicate';
     is $jobs->count, $jobs_nr + 2, 'job retriggered as it FAILED (with retry)';
     $job->update;
     $job->discard_changes;
@@ -931,6 +937,58 @@
     is $lastest_job->latest_job->id, $lastest_job->id, 'found the latest job 
from latest job itself';
 };
 
+subtest 'AMQP event emission for job restarts within Minion tasks' => sub {
+    my $plugin_mock = Test::MockModule->new('OpenQA::WebAPI::Plugin::AMQP');
+    my $conf = "[global]\nplugins=AMQP\n[amqp]\npublish_attempts = 
2\npublish_retry_delay = 0\n";
+    my $tempdir = tempdir;
+    path($ENV{OPENQA_CONFIG} = 
$tempdir)->make_path->child('openqa.ini')->spew($conf);
+    my %published;
+    my @event_body;
+    my $io_loop_mock = Test::MockModule->new('Mojo::IOLoop');
+    $io_loop_mock->redefine(start => sub { });
+    $plugin_mock->redefine(
+        publish_amqp => sub ($self, $topic, $data) {
+            $published{$topic} = $data;
+            Mojo::IOLoop->next_tick(sub { 
OpenQA::Events->singleton->emit('amqp_handled'); });
+        });
+
+    my $events_mock = Test::MockModule->new('OpenQA::Events');
+    $events_mock->redefine(
+        emit => sub ($self, $type, $args) {
+            if ($type eq 'openqa_job_restart') {
+                @event_body = ($type, $args);
+            }
+            $events_mock->original('emit')->($self, $type, $args);
+        });
+    # use a new app; otherwise it runs slowly, maybe trying to run none-mocked 
stuff
+    my $t = Test::Mojo->new('OpenQA::WebAPI');
+    my $minion = $t->app->minion;
+    is $t->app->config->{amqp}->{enabled}, 1, 'AMQP enabled from config file';
+
+    my %retry_settings = %settings;
+    $retry_settings{TEST} = 'test_amqp_restart';
+    $retry_settings{RETRY} = '1';
+    my $job = $jobs->create_from_settings(\%retry_settings);
+    $job->discard_changes;
+    my $job_id = $job->id;
+
+    %published = ();
+    $job->done(result => FAILED);
+    stdout_like { perform_minion_jobs($minion) } qr/Job \d+ duplicated as \d+/;
+    is scalar keys %published, 1, 'exactly one job restart event emitted';
+    my $event = $published{'suse.openqa.job.restart'};
+    is $event->{id}, $job_id, 'event contains original job ID';
+    is $event->{auto}, 1, 'event marked as auto restart';
+    my ($user_id, $connection, $type, $data) = @{$event_body[1]};
+    is $user_id, undef, 'user_id is undef for Minion restart';
+    is $connection, undef, 'connection is undef for Minion restart';
+    is $type, 'openqa_job_restart', 'type matches event type';
+    is_deeply $data, $event, 'published event equals emitted event';
+    ok exists $data->{result}->{$job_id}, 'old job id is in result';
+    $job->discard_changes;
+    is $job->clone_id, $data->{result}->{$job_id}, 'clone_id points to 
reported id';
+};
+
 subtest '"race" between status updates and stale job detection' => sub {
     my $job = $jobs->create({TEST => 'test-job'});
     is_deeply $job->update_status({}), {result => 0}, 'status update rejected 
for scheduled job';
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/t/10-tests_overview.t 
new/openQA-5.1764349525.ffb59486/t/10-tests_overview.t
--- old/openQA-5.1763743683.1da97aa2/t/10-tests_overview.t      2025-11-21 
17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/t/10-tests_overview.t      2025-11-28 
18:05:25.000000000 +0100
@@ -151,10 +151,8 @@
 };
 
 subtest 'limit parameter' => sub {
-    $t->get_ok(
-        '/tests/overview?distri=opensuse&version=Factory&limit=2&limit=2',
-        'no database error when specifying more than one limit'
-    );
+    
$t->get_ok('/tests/overview?distri=opensuse&version=Factory&build=0048&limit=2&limit=2',
+        'no database error when specifying more than one limit');
     is $t->tx->res->dom->find('table.overview td.name')->size, 2, 'number of 
jobs limited';
 };
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/t/23-amqp.t 
new/openQA-5.1764349525.ffb59486/t/23-amqp.t
--- old/openQA-5.1763743683.1da97aa2/t/23-amqp.t        2025-11-21 
17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/t/23-amqp.t        2025-11-28 
18:05:25.000000000 +0100
@@ -276,7 +276,8 @@
 $amqp->register($app);
 
 subtest 'amqp_publish call without headers' => sub {
-    $amqp->publish_amqp('some.topic', 'some message');
+    combined_like { $amqp->publish_amqp('some.topic', 'some message') } 
qr/AMQP URL: amqp:\/\/localhost:5672/,
+      'URL is logged without credentials';
     is($last_publisher->url, 
'amqp://guest:guest@localhost:5672/?exchange=pubsub', 'url specified');
     is($published{body}, 'some message', 'message body correctly passed');
     is_deeply($published{headers}, {}, 'headers is empty hashref');
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/t/39-scheduled_products-table.t 
new/openQA-5.1764349525.ffb59486/t/39-scheduled_products-table.t
--- old/openQA-5.1763743683.1da97aa2/t/39-scheduled_products-table.t    
2025-11-21 17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/t/39-scheduled_products-table.t    
2025-11-28 18:05:25.000000000 +0100
@@ -16,6 +16,7 @@
 use OpenQA::Test::TimeLimit '6';
 use OpenQA::Test::Case;
 use OpenQA::Utils;
+use DateTime;
 
 OpenQA::Test::Case->new->init_data;
 my $t = Test::Mojo->new('OpenQA::WebAPI');
@@ -148,4 +149,12 @@
     is @ids, 2, 'two jobs created';
 };
 
+subtest 'cleanup' => sub {
+    $scheduled_products->search({id => 1})->update({t_created => 
DateTime->now->subtract(days => 35)});
+    $scheduled_products->delete_expired_entries;
+    ok !$scheduled_products->find(1), 'old scheduled product without jobs 
deleted';
+    ok $scheduled_products->find(2), 'new scheduled product without jobs still 
present)';
+    ok $scheduled_products->find(3), 'scheduled product with jobs still 
present';
+};
+
 done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/t/41-audit-log.t 
new/openQA-5.1764349525.ffb59486/t/41-audit-log.t
--- old/openQA-5.1763743683.1da97aa2/t/41-audit-log.t   2025-11-21 
17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/t/41-audit-log.t   2025-11-28 
18:05:25.000000000 +0100
@@ -75,52 +75,52 @@
 
 # note: The tested time constraints are defined in 
t/data/41-audit-log/openqa.ini.
 
-$events->delete_entries_exceeding_storage_duration;
+$events->delete_expired_entries;
 is_deeply(all_events_ids, [sort keys %fake_events], 'all events considered 
recent enough to be kept');
 
 assume_all_events_before_x_days(15);
 assume_events_being_deleted(1080, 1081);
-$events->delete_entries_exceeding_storage_duration;
+$events->delete_expired_entries;
 is_deeply(all_events_ids, [sort keys %fake_events], 'other events deleted 
after 10 days');
 
 assume_all_events_before_x_days(25);
 assume_events_being_deleted(1000);
-$events->delete_entries_exceeding_storage_duration;
+$events->delete_expired_entries;
 is_deeply(all_events_ids, [sort keys %fake_events], 'startup events deleted 
after 20 days');
 
 assume_all_events_before_x_days(35);
 assume_events_being_deleted(1030, 1031);
-$events->delete_entries_exceeding_storage_duration;
+$events->delete_expired_entries;
 is_deeply(all_events_ids, [sort keys %fake_events], 'job group events deleted 
after 30 days');
 
 assume_all_events_before_x_days(45);
 assume_events_being_deleted(1020, 1021);
-$events->delete_entries_exceeding_storage_duration;
+$events->delete_expired_entries;
 is_deeply(all_events_ids, [sort keys %fake_events], 'job template events 
deleted after 40 days');
 
 assume_all_events_before_x_days(55);
 assume_events_being_deleted(1010, 1011);
-$events->delete_entries_exceeding_storage_duration;
+$events->delete_expired_entries;
 is_deeply(all_events_ids, [sort keys %fake_events], 'table events deleted 
after 50 days');
 
 assume_all_events_before_x_days(65);
 assume_events_being_deleted(1060, 1061);
-$events->delete_entries_exceeding_storage_duration;
+$events->delete_expired_entries;
 is_deeply(all_events_ids, [sort keys %fake_events], 'iso events deleted after 
60 days');
 
 assume_all_events_before_x_days(75);
 assume_events_being_deleted(1050);
-$events->delete_entries_exceeding_storage_duration;
+$events->delete_expired_entries;
 is_deeply(all_events_ids, [sort keys %fake_events], 'user events deleted after 
70 days');
 
 assume_all_events_before_x_days(85);
 assume_events_being_deleted(1070, 1071);
-$events->delete_entries_exceeding_storage_duration;
+$events->delete_expired_entries;
 is_deeply(all_events_ids, [sort keys %fake_events], 'asset events deleted 
after 80 days');
 
 assume_all_events_before_x_days(95);
 assume_events_being_deleted(1040);
-$events->delete_entries_exceeding_storage_duration;
+$events->delete_expired_entries;
 is_deeply(all_events_ids, [sort keys %fake_events], 'needle events deleted 
after 90 days');
 
 done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/t/api/04-jobs.t 
new/openQA-5.1764349525.ffb59486/t/api/04-jobs.t
--- old/openQA-5.1763743683.1da97aa2/t/api/04-jobs.t    2025-11-21 
17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/t/api/04-jobs.t    2025-11-28 
18:05:25.000000000 +0100
@@ -278,7 +278,8 @@
     subtest 'limit parameter' => sub {
         $query->query(build => '0048', limit => 1);
         $t->get_ok($query->path_query)->status_is(200);
-        is(scalar(@{$t->tx->res->json}), 1, 'Expect only one job entry');
+        my $json = $t->tx->res->json;
+        is scalar @$json, 1, 'Expect only one job entry' or always_explain 
$json;
         $t->json_is('/0/id' => 99939, 'Check correct order');
     };
 };
@@ -1008,8 +1009,14 @@
         $t->post_ok('/api/v1/jobs', form => 
\%jobs_post_params)->status_is(200);
         $t->get_ok('/api/v1/jobs/' . $t->tx->res->json->{id})->status_is(200);
         $t->json_is('/job/priority', 50 + $max / 100, 'increased prio value');
-        my $limits = OpenQA::App->singleton->config->{misc_limits};
 
+        local $jobs_post_params{TIMEOUT_SCALE} = 2;
+        $t->post_ok('/api/v1/jobs', form => 
\%jobs_post_params)->status_is(200);
+        $t->get_ok('/api/v1/jobs/' . $t->tx->res->json->{id})->status_is(200);
+        $t->json_is('/job/priority', 50 + $max * 2 / 100, 'increased prio 
value with TIMEOUT_SCALE');
+        delete $jobs_post_params{TIMEOUT_SCALE};
+
+        my $limits = OpenQA::App->singleton->config->{misc_limits};
         $limits->{max_job_time_prio_scale} = 10;
         $t->post_ok('/api/v1/jobs', form => 
\%jobs_post_params)->status_is(200);
         $t->get_ok('/api/v1/jobs/' . $t->tx->res->json->{id})->status_is(200);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/t/config.t 
new/openQA-5.1764349525.ffb59486/t/config.t
--- old/openQA-5.1763743683.1da97aa2/t/config.t 2025-11-21 17:48:03.000000000 
+0100
+++ new/openQA-5.1764349525.ffb59486/t/config.t 2025-11-28 18:05:25.000000000 
+0100
@@ -192,6 +192,7 @@
             worker_limit_retry_delay => ONE_HOUR / 4,
             mcp_max_result_size => 500000,
             max_job_time_prio_scale => 100,
+            scheduled_product_min_storage_duration => 34,
         },
         archiving => {
             archive_preserved_important_jobs => 0,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/t/ui/10-tests_overview.t 
new/openQA-5.1764349525.ffb59486/t/ui/10-tests_overview.t
--- old/openQA-5.1763743683.1da97aa2/t/ui/10-tests_overview.t   2025-11-21 
17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/t/ui/10-tests_overview.t   2025-11-28 
18:05:25.000000000 +0100
@@ -487,8 +487,9 @@
         element_not_present("#$_") for qw(flavor_DVD_arch_i586 
flavor_GNOME-Live_arch_i686 flavor_NET_arch_x86_64);
         is element_prop('filter-machine'), 'uefi', 'machine filter still 
visible in form';
 
-        my @job_rows = map { $_->get_text } @{$driver->find_elements('#content 
tbody tr')};
-        is_deeply \@job_rows, ['kde@uefi'], 'only the job with machine uefi is 
shown' or always_explain \@job_rows;
+        my $get_job_ids = qq|return 
Array.from(document.querySelectorAll('span[id^="res-"]')).map(e => 
e.id.substr(4))|;
+        my $job_ids = $driver->execute_script($get_job_ids);
+        is_deeply $job_ids, ['99982'], 'only the job with machine uefi is 
shown' or always_explain $job_ids;
 
         $driver->find_element('#filter-panel .card-header')->click();
         $driver->find_element('#filter-machine')->clear();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1763743683.1da97aa2/templates/webapi/test/overview.html.ep 
new/openQA-5.1764349525.ffb59486/templates/webapi/test/overview.html.ep
--- old/openQA-5.1763743683.1da97aa2/templates/webapi/test/overview.html.ep     
2025-11-21 17:48:03.000000000 +0100
+++ new/openQA-5.1764349525.ffb59486/templates/webapi/test/overview.html.ep     
2025-11-28 18:05:25.000000000 +0100
@@ -44,9 +44,9 @@
             % if ($allow_commenting) {
             <button type="button" class="btn btn-secondary btn-circle btn-sm 
trigger-edit-button" onclick="showAddCommentsDialog()" title="Restart or 
comment jobs">
                 <span class="fa-stack group-comment-icon-stack">
-                 <i class="fa fa-comment fa-stack-2x text-danger-info" 
aria-hidden="true"></i>
-                 <i class="fa fa-undo fa-stack-1x" aria-hidden="true"></i>
-               </span>
+                    <i class="fa fa-comment fa-stack-2x text-danger-info" 
aria-hidden="true"></i>
+                    <i class="fa fa-undo fa-stack-1x" aria-hidden="true"></i>
+                </span>
             </button>
             % }
             % my @badges = qw(success secondary  warning     danger info      
primary light   light);

++++++ openQA.obsinfo ++++++
--- /var/tmp/diff_new_pack.95quJ0/_old  2025-12-01 11:13:24.202714944 +0100
+++ /var/tmp/diff_new_pack.95quJ0/_new  2025-12-01 11:13:24.206715113 +0100
@@ -1,5 +1,5 @@
 name: openQA
-version: 5.1763743683.1da97aa2
-mtime: 1763743683
-commit: 1da97aa28ce09ffbdf8755234076efc7641d3b45
+version: 5.1764349525.ffb59486
+mtime: 1764349525
+commit: ffb5948677625e9c9e3c79d59e74a8289bac40b0
 

Reply via email to