Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package openQA for openSUSE:Factory checked in at 2026-03-09 16:32:48 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/openQA (Old) and /work/SRC/openSUSE:Factory/.openQA.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "openQA" Mon Mar 9 16:32:48 2026 rev:819 rq:1337659 version:5.1773056733.e071deaf Changes: -------- --- /work/SRC/openSUSE:Factory/openQA/openQA.changes 2026-03-06 18:18:35.232144411 +0100 +++ /work/SRC/openSUSE:Factory/.openQA.new.8177/openQA.changes 2026-03-09 16:33:01.982428227 +0100 @@ -1,0 +2,12 @@ +Mon Mar 09 13:08:18 UTC 2026 - [email protected] + +- Update to version 5.1773056733.e071deaf: + * feat: allow filtering by job result and state in /tests/latest + * style(gitlint): allow unwrappable longer URLs in git commit message body + * feat: unify priority management of max_job_time and throttling + * ci(helm): pull container images in advance + * ci(helm): make sure that install-chart runs after lint-chart + * feat: optimize size of devel:openQA:ci/base container + * feat: allow users to delete/anonymize their own account + +------------------------------------------------------------------- Old: ---- openQA-5.1772722702.3877b2ca.obscpio New: ---- openQA-5.1773056733.e071deaf.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ openQA-client-test.spec ++++++ --- /var/tmp/diff_new_pack.zfvx9V/_old 2026-03-09 16:33:04.502530980 +0100 +++ /var/tmp/diff_new_pack.zfvx9V/_new 2026-03-09 16:33:04.506531143 +0100 @@ -18,7 +18,7 @@ %define short_name openQA-client Name: %{short_name}-test -Version: 5.1772722702.3877b2ca +Version: 5.1773056733.e071deaf Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA-devel-test.spec ++++++ --- /var/tmp/diff_new_pack.zfvx9V/_old 2026-03-09 16:33:04.762541582 +0100 +++ /var/tmp/diff_new_pack.zfvx9V/_new 2026-03-09 16:33:04.790542723 +0100 @@ -18,7 +18,7 @@ %define short_name openQA-devel Name: %{short_name}-test -Version: 5.1772722702.3877b2ca +Version: 5.1773056733.e071deaf Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA-test.spec ++++++ --- /var/tmp/diff_new_pack.zfvx9V/_old 2026-03-09 16:33:05.134556750 +0100 +++ /var/tmp/diff_new_pack.zfvx9V/_new 2026-03-09 16:33:05.150557402 +0100 @@ -18,7 +18,7 @@ %define short_name openQA Name: %{short_name}-test -Version: 5.1772722702.3877b2ca +Version: 5.1773056733.e071deaf Release: 0 Summary: Test package for openQA License: GPL-2.0-or-later ++++++ openQA-worker-test.spec ++++++ --- /var/tmp/diff_new_pack.zfvx9V/_old 2026-03-09 16:33:05.426568656 +0100 +++ /var/tmp/diff_new_pack.zfvx9V/_new 2026-03-09 16:33:05.442569308 +0100 @@ -18,7 +18,7 @@ %define short_name openQA-worker Name: %{short_name}-test -Version: 5.1772722702.3877b2ca +Version: 5.1773056733.e071deaf Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA.spec ++++++ --- /var/tmp/diff_new_pack.zfvx9V/_old 2026-03-09 16:33:05.766582520 +0100 +++ /var/tmp/diff_new_pack.zfvx9V/_new 2026-03-09 16:33:05.806584150 +0100 @@ -99,7 +99,7 @@ %define devel_requires %devel_no_selenium_requires chromedriver Name: openQA -Version: 5.1772722702.3877b2ca +Version: 5.1773056733.e071deaf Release: 0 Summary: The openQA web-frontend, scheduler and tools License: GPL-2.0-or-later ++++++ openQA-5.1772722702.3877b2ca.obscpio -> openQA-5.1773056733.e071deaf.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/.github/workflows/check-helm-chart.yml new/openQA-5.1773056733.e071deaf/.github/workflows/check-helm-chart.yml --- old/openQA-5.1772722702.3877b2ca/.github/workflows/check-helm-chart.yml 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/.github/workflows/check-helm-chart.yml 2026-03-09 12:45:33.000000000 +0100 @@ -26,6 +26,9 @@ run: make test-helm-lint install-chart: runs-on: ubuntu-latest + needs: lint-chart + env: + REGISTRY_PATH: registry.opensuse.org/devel/openqa/containers16.0 steps: - name: Checkout repository uses: actions/checkout@v4 @@ -46,5 +49,16 @@ - name: Create kind cluster uses: helm/kind-action@v1 + - name: Download images + run: | + RETRY=3 SLEEP=15 tools/retry docker pull $REGISTRY_PATH/openqa_webui:latest + RETRY=3 SLEEP=15 tools/retry docker pull $REGISTRY_PATH/openqa_worker:latest + + - name: Load images into the Cluster + run: | + kind load docker-image --name chart-testing \ + $REGISTRY_PATH/openqa_webui:latest \ + $REGISTRY_PATH/openqa_worker:latest + - name: Run chart-testing - run: make RETRY=3 test-helm-install + run: make test-helm-install diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/.gitlint new/openQA-5.1773056733.e071deaf/.gitlint --- old/openQA-5.1772722702.3877b2ca/.gitlint 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/.gitlint 2026-03-09 12:45:33.000000000 +0100 @@ -1,3 +1,8 @@ [general] contrib=contrib-title-conventional-commits ignore=body-min-length,body-is-missing +regex-style-search=True + +[ignore-body-lines] +# Accept lines stating only a long unwrappable URL +regex=^https?:\/\/\S+$ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/assets/assetpack.def new/openQA-5.1773056733.e071deaf/assets/assetpack.def --- old/openQA-5.1772722702.3877b2ca/assets/assetpack.def 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/assets/assetpack.def 2026-03-09 12:45:33.000000000 +0100 @@ -131,6 +131,7 @@ < javascripts/admintable.js < javascripts/admin_user.js < javascripts/admin_api_keys.js +< javascripts/delete_account.js < javascripts/admin_needle.js < javascripts/admin_worker.js < javascripts/admin_groups.js diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/assets/javascripts/delete_account.js new/openQA-5.1773056733.e071deaf/assets/javascripts/delete_account.js --- old/openQA-5.1772722702.3877b2ca/assets/javascripts/delete_account.js 1970-01-01 01:00:00.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/assets/javascripts/delete_account.js 2026-03-09 12:45:33.000000000 +0100 @@ -0,0 +1,21 @@ +function setup_delete_account() { + const input = document.getElementById('confirm-delete'); + const btn = document.getElementById('confirm-delete-btn'); + if (!input || !btn) return; + + input.addEventListener('input', function () { + btn.disabled = input.value !== input.dataset.expected; + }); + + btn.addEventListener('click', function () { + const deleteUrl = btn.dataset.deleteUrl; + const redirectUrl = btn.dataset.redirectUrl; + fetchWithCSRF(deleteUrl, {method: 'DELETE'}).then(function (response) { + if (response.ok) { + window.location.href = redirectUrl; + } else { + alert('Failed to delete account. Please try again.'); + } + }); + }); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/container/devel:openQA:ci/base/Dockerfile new/openQA-5.1773056733.e071deaf/container/devel:openQA:ci/base/Dockerfile --- old/openQA-5.1772722702.3877b2ca/container/devel:openQA:ci/base/Dockerfile 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/container/devel:openQA:ci/base/Dockerfile 2026-03-09 12:45:33.000000000 +0100 @@ -6,18 +6,15 @@ #!NoSquash FROM opensuse/leap:16.0 -# only dependencies for CircleCI to be able to load/save the package cache +# dependencies for CircleCI to be able to load/save the package cache # and fix permissions for the unprivileged user we use -RUN zypper -n in tar gzip sudo - -# these are autoinst dependencies -RUN zypper install -y gcc-c++ cmake ninja pkgconfig\(opencv4\) pkg-config perl\(Module::CPANfile\) pkgconfig\(fftw3\) pkgconfig\(libpng\) pkgconfig\(sndfile\) pkgconfig\(theoraenc\) tesseract-ocr - -# openQA dependencies -RUN zypper install -y perl-CSS-Sass ruby-devel npm python3-base python3-requests git-core rsync curl 'postgresql-devel>=14' 'postgresql-server>=14' qemu qemu-tools tar xorg-x11-fonts sudo make - -# openQA chromedriver for Selenium tests -RUN zypper install -y chromedriver +# + autoinst dependencies +# + openQA dependencies +# + openQA chromedriver for Selenium tests +RUN zypper -n in tar gzip sudo \ + gcc-c++ cmake ninja pkgconfig\(opencv4\) pkg-config perl\(Module::CPANfile\) pkgconfig\(fftw3\) pkgconfig\(libpng\) pkgconfig\(sndfile\) pkgconfig\(theoraenc\) tesseract-ocr \ + perl-CSS-Sass ruby-devel npm python3-base python3-requests git-core rsync curl 'postgresql-devel>=14' 'postgresql-server>=14' qemu qemu-tools tar xorg-x11-fonts sudo make \ + chromedriver && zypper clean -a ENV LANG=en_US.UTF-8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/dbicdh/PostgreSQL/deploy/103/001-auto-__VERSION.sql new/openQA-5.1773056733.e071deaf/dbicdh/PostgreSQL/deploy/103/001-auto-__VERSION.sql --- old/openQA-5.1772722702.3877b2ca/dbicdh/PostgreSQL/deploy/103/001-auto-__VERSION.sql 1970-01-01 01:00:00.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/dbicdh/PostgreSQL/deploy/103/001-auto-__VERSION.sql 2026-03-09 12:45:33.000000000 +0100 @@ -0,0 +1,18 @@ +-- +-- Created by SQL::Translator::Producer::PostgreSQL +-- Created on Tue Jul 22 11:14:48 2025 +-- +; +-- +-- Table: dbix_class_deploymenthandler_versions +-- +CREATE TABLE dbix_class_deploymenthandler_versions ( + id serial NOT NULL, + version character varying(50) NOT NULL, + ddl text, + upgrade_sql text, + PRIMARY KEY (id), + CONSTRAINT dbix_class_deploymenthandler_versions_version UNIQUE (version) +); + +; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/dbicdh/PostgreSQL/deploy/103/001-auto.sql new/openQA-5.1773056733.e071deaf/dbicdh/PostgreSQL/deploy/103/001-auto.sql --- old/openQA-5.1772722702.3877b2ca/dbicdh/PostgreSQL/deploy/103/001-auto.sql 1970-01-01 01:00:00.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/dbicdh/PostgreSQL/deploy/103/001-auto.sql 2026-03-09 12:45:33.000000000 +0100 @@ -0,0 +1,800 @@ +-- +-- Created by SQL::Translator::Producer::PostgreSQL +-- Created on Tue Jul 22 11:14:48 2025 +-- +; +-- +-- Table: bugs +-- +CREATE TABLE bugs ( + id bigserial NOT NULL, + bugid text NOT NULL, + title text, + priority text, + assigned boolean, + assignee text, + open boolean, + status text, + resolution text, + existing boolean DEFAULT '1' NOT NULL, + refreshed boolean DEFAULT '0' NOT NULL, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT bugs_bugid UNIQUE (bugid) +); + +; +-- +-- Table: gru_tasks +-- +CREATE TABLE gru_tasks ( + id bigserial NOT NULL, + taskname text NOT NULL, + args text NOT NULL, + run_at timestamp NOT NULL, + priority integer NOT NULL, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id) +); +CREATE INDEX gru_tasks_run_at_reversed on gru_tasks (run_at DESC); + +; +-- +-- Table: job_group_parents +-- +CREATE TABLE job_group_parents ( + id bigserial NOT NULL, + name text NOT NULL, + size_limit_gb integer, + exclusively_kept_asset_size bigint, + default_keep_logs_in_days integer, + default_keep_important_logs_in_days integer, + default_keep_results_in_days integer, + default_keep_important_results_in_days integer, + default_keep_jobs_in_days integer, + default_keep_important_jobs_in_days integer, + default_priority integer, + sort_order integer, + description text, + build_version_sort integer DEFAULT 1 NOT NULL, + carry_over_bugrefs boolean, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT job_group_parents_name UNIQUE (name) +); + +; +-- +-- Table: job_modules +-- +CREATE TABLE job_modules ( + id bigserial NOT NULL, + job_id bigint NOT NULL, + name text NOT NULL, + script text NOT NULL, + category text NOT NULL, + milestone integer DEFAULT 0 NOT NULL, + important integer DEFAULT 0 NOT NULL, + fatal integer DEFAULT 0 NOT NULL, + always_rollback integer DEFAULT 0 NOT NULL, + result character varying DEFAULT 'none' NOT NULL, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT job_modules_job_id_name_category_script UNIQUE (job_id, name, category, script) +); +CREATE INDEX job_modules_idx_job_id on job_modules (job_id); +CREATE INDEX idx_job_modules_result on job_modules (result); + +; +-- +-- Table: job_settings +-- +CREATE TABLE job_settings ( + id bigserial NOT NULL, + key text NOT NULL, + value text NOT NULL, + job_id bigint NOT NULL, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id) +); +CREATE INDEX job_settings_idx_job_id on job_settings (job_id); +CREATE INDEX idx_value_settings on job_settings (key, value); +CREATE INDEX idx_job_id_value_settings on job_settings (job_id, key, value); + +; +-- +-- Table: job_template_settings +-- +CREATE TABLE job_template_settings ( + id bigserial NOT NULL, + job_template_id bigint NOT NULL, + key text NOT NULL, + value text NOT NULL, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT job_template_settings_job_template_id_key UNIQUE (job_template_id, key) +); +CREATE INDEX job_template_settings_idx_job_template_id on job_template_settings (job_template_id); + +; +-- +-- Table: machine_settings +-- +CREATE TABLE machine_settings ( + id bigserial NOT NULL, + machine_id bigint NOT NULL, + key text NOT NULL, + value text NOT NULL, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT machine_settings_machine_id_key UNIQUE (machine_id, key) +); +CREATE INDEX machine_settings_idx_machine_id on machine_settings (machine_id); + +; +-- +-- Table: machines +-- +CREATE TABLE machines ( + id bigserial NOT NULL, + name text NOT NULL, + backend text NOT NULL, + description text, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT machines_name UNIQUE (name) +); + +; +-- +-- Table: needle_dirs +-- +CREATE TABLE needle_dirs ( + id bigserial NOT NULL, + path text NOT NULL, + name text NOT NULL, + PRIMARY KEY (id), + CONSTRAINT needle_dirs_path UNIQUE (path) +); + +; +-- +-- Table: product_settings +-- +CREATE TABLE product_settings ( + id bigserial NOT NULL, + product_id bigint NOT NULL, + key text NOT NULL, + value text NOT NULL, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT product_settings_product_id_key UNIQUE (product_id, key) +); +CREATE INDEX product_settings_idx_product_id on product_settings (product_id); + +; +-- +-- Table: products +-- +CREATE TABLE products ( + id bigserial NOT NULL, + name text NOT NULL, + distri text NOT NULL, + version text DEFAULT '' NOT NULL, + arch text NOT NULL, + flavor text NOT NULL, + description text, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT products_distri_version_arch_flavor UNIQUE (distri, version, arch, flavor) +); + +; +-- +-- Table: screenshots +-- +CREATE TABLE screenshots ( + id bigserial NOT NULL, + filename text NOT NULL, + t_created timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT screenshots_filename UNIQUE (filename) +); + +; +-- +-- Table: secrets +-- +CREATE TABLE secrets ( + id bigserial NOT NULL, + secret text NOT NULL, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT secrets_secret UNIQUE (secret) +); + +; +-- +-- Table: test_suite_settings +-- +CREATE TABLE test_suite_settings ( + id bigserial NOT NULL, + test_suite_id bigint NOT NULL, + key text NOT NULL, + value text NOT NULL, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT test_suite_settings_test_suite_id_key UNIQUE (test_suite_id, key) +); +CREATE INDEX test_suite_settings_idx_test_suite_id on test_suite_settings (test_suite_id); + +; +-- +-- Table: test_suites +-- +CREATE TABLE test_suites ( + id bigserial NOT NULL, + name text NOT NULL, + description text, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT test_suites_name UNIQUE (name) +); + +; +-- +-- Table: users +-- +CREATE TABLE users ( + id bigserial NOT NULL, + username text NOT NULL, + provider text DEFAULT '' NOT NULL, + email text, + fullname text, + nickname text, + is_operator integer DEFAULT 0 NOT NULL, + is_admin integer DEFAULT 0 NOT NULL, + feature_version integer DEFAULT 1 NOT NULL, + deleted_at timestamp, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT users_username_provider UNIQUE (username, provider) +); + +; +-- +-- Table: worker_properties +-- +CREATE TABLE worker_properties ( + id bigserial NOT NULL, + key text NOT NULL, + value text NOT NULL, + worker_id bigint NOT NULL, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id) +); +CREATE INDEX worker_properties_idx_worker_id on worker_properties (worker_id); + +; +-- +-- Table: api_keys +-- +CREATE TABLE api_keys ( + id bigserial NOT NULL, + key text NOT NULL, + secret text NOT NULL, + user_id bigint NOT NULL, + t_expiration timestamp, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT api_keys_key UNIQUE (key) +); +CREATE INDEX api_keys_idx_user_id on api_keys (user_id); + +; +-- +-- Table: audit_events +-- +CREATE TABLE audit_events ( + id bigserial NOT NULL, + user_id bigint, + connection_id text, + event text NOT NULL, + event_data text, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id) +); +CREATE INDEX audit_events_idx_user_id on audit_events (user_id); + +; +-- +-- Table: comments +-- +CREATE TABLE comments ( + id bigserial NOT NULL, + job_id bigint, + group_id bigint, + parent_group_id bigint, + text text NOT NULL, + user_id bigint NOT NULL, + flags integer DEFAULT 0, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id) +); +CREATE INDEX comments_idx_group_id on comments (group_id); +CREATE INDEX comments_idx_job_id on comments (job_id); +CREATE INDEX comments_idx_parent_group_id on comments (parent_group_id); +CREATE INDEX comments_idx_user_id on comments (user_id); + +; +-- +-- Table: job_groups +-- +CREATE TABLE job_groups ( + id bigserial NOT NULL, + name text NOT NULL, + parent_id bigint, + size_limit_gb integer, + exclusively_kept_asset_size bigint, + keep_logs_in_days integer, + keep_important_logs_in_days integer, + keep_results_in_days integer, + keep_important_results_in_days integer, + keep_jobs_in_days integer, + keep_important_jobs_in_days integer, + default_priority integer, + sort_order integer, + description text, + template text, + build_version_sort integer DEFAULT 1 NOT NULL, + carry_over_bugrefs boolean, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT job_groups_name_parent_id UNIQUE (name, parent_id) +); +CREATE INDEX job_groups_idx_parent_id on job_groups (parent_id); + +; +-- +-- Table: workers +-- +CREATE TABLE workers ( + id bigserial NOT NULL, + host text NOT NULL, + instance integer NOT NULL, + job_id bigint, + t_seen timestamp, + upload_progress jsonb, + error text, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT workers_host_instance UNIQUE (host, instance), + CONSTRAINT workers_job_id UNIQUE (job_id) +); +CREATE INDEX workers_idx_job_id on workers (job_id); + +; +-- +-- Table: needles +-- +CREATE TABLE needles ( + id bigserial NOT NULL, + dir_id bigint NOT NULL, + filename text NOT NULL, + last_seen_time timestamp, + last_seen_module_id bigint, + last_matched_time timestamp, + last_matched_module_id bigint, + last_updated timestamp, + file_present boolean DEFAULT '1' NOT NULL, + tags text[], + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT needles_dir_id_filename UNIQUE (dir_id, filename) +); +CREATE INDEX needles_idx_dir_id on needles (dir_id); +CREATE INDEX needles_idx_last_matched_module_id on needles (last_matched_module_id); +CREATE INDEX needles_idx_last_seen_module_id on needles (last_seen_module_id); + +; +-- +-- Table: scheduled_products +-- +CREATE TABLE scheduled_products ( + id bigserial NOT NULL, + distri text DEFAULT '' NOT NULL, + version text DEFAULT '' NOT NULL, + flavor text DEFAULT '' NOT NULL, + arch text DEFAULT '' NOT NULL, + build text DEFAULT '' NOT NULL, + iso text DEFAULT '' NOT NULL, + status text DEFAULT 'added' NOT NULL, + settings jsonb NOT NULL, + results jsonb, + user_id bigint, + gru_task_id bigint, + minion_job_id bigint, + webhook_id text, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id) +); +CREATE INDEX scheduled_products_idx_gru_task_id on scheduled_products (gru_task_id); +CREATE INDEX scheduled_products_idx_user_id on scheduled_products (user_id); +CREATE INDEX scheduled_products_idx_webhook_id on scheduled_products (webhook_id); + +; +-- +-- Table: job_templates +-- +CREATE TABLE job_templates ( + id bigserial NOT NULL, + product_id bigint NOT NULL, + machine_id bigint NOT NULL, + test_suite_id bigint NOT NULL, + name text DEFAULT '' NOT NULL, + description text DEFAULT '' NOT NULL, + prio integer, + group_id bigint NOT NULL, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT scenario UNIQUE (product_id, machine_id, name, test_suite_id) +); +CREATE INDEX job_templates_idx_group_id on job_templates (group_id); +CREATE INDEX job_templates_idx_machine_id on job_templates (machine_id); +CREATE INDEX job_templates_idx_product_id on job_templates (product_id); +CREATE INDEX job_templates_idx_test_suite_id on job_templates (test_suite_id); + +; +-- +-- Table: jobs +-- +CREATE TABLE jobs ( + id bigserial NOT NULL, + result_dir text, + archived boolean DEFAULT '0' NOT NULL, + state character varying DEFAULT 'scheduled' NOT NULL, + priority integer DEFAULT 50 NOT NULL, + result character varying DEFAULT 'none' NOT NULL, + reason character varying, + clone_id bigint, + blocked_by_id bigint, + TEST text NOT NULL, + DISTRI text DEFAULT '' NOT NULL, + VERSION text DEFAULT '' NOT NULL, + FLAVOR text DEFAULT '' NOT NULL, + ARCH text DEFAULT '' NOT NULL, + BUILD text DEFAULT '' NOT NULL, + MACHINE text, + group_id bigint, + assigned_worker_id bigint, + t_started timestamp, + t_finished timestamp, + logs_present boolean DEFAULT '1' NOT NULL, + passed_module_count integer DEFAULT 0 NOT NULL, + failed_module_count integer DEFAULT 0 NOT NULL, + softfailed_module_count integer DEFAULT 0 NOT NULL, + skipped_module_count integer DEFAULT 0 NOT NULL, + externally_skipped_module_count integer DEFAULT 0 NOT NULL, + scheduled_product_id bigint, + result_size bigint, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id) +); +CREATE INDEX jobs_idx_assigned_worker_id on jobs (assigned_worker_id); +CREATE INDEX jobs_idx_blocked_by_id on jobs (blocked_by_id); +CREATE INDEX jobs_idx_clone_id on jobs (clone_id); +CREATE INDEX jobs_idx_group_id on jobs (group_id); +CREATE INDEX jobs_idx_scheduled_product_id on jobs (scheduled_product_id); +CREATE INDEX idx_jobs_state on jobs (state); +CREATE INDEX idx_jobs_result on jobs (result); +CREATE INDEX idx_jobs_build_group on jobs (BUILD, group_id); +CREATE INDEX idx_jobs_scenario on jobs (VERSION, DISTRI, FLAVOR, TEST, MACHINE, ARCH); + +; +-- +-- Table: assets +-- +CREATE TABLE assets ( + id bigserial NOT NULL, + type text NOT NULL, + name text NOT NULL, + size bigint, + checksum text, + last_use_job_id bigint, + fixed boolean DEFAULT '0' NOT NULL, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (id), + CONSTRAINT assets_type_name UNIQUE (type, name) +); +CREATE INDEX assets_idx_last_use_job_id on assets (last_use_job_id); + +; +-- +-- Table: developer_sessions +-- +CREATE TABLE developer_sessions ( + job_id bigint NOT NULL, + user_id bigint NOT NULL, + ws_connection_count integer DEFAULT 0 NOT NULL, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + PRIMARY KEY (job_id) +); +CREATE INDEX developer_sessions_idx_user_id on developer_sessions (user_id); + +; +-- +-- Table: gru_dependencies +-- +CREATE TABLE gru_dependencies ( + job_id bigint NOT NULL, + gru_task_id bigint NOT NULL, + PRIMARY KEY (job_id, gru_task_id) +); +CREATE INDEX gru_dependencies_idx_gru_task_id on gru_dependencies (gru_task_id); +CREATE INDEX gru_dependencies_idx_job_id on gru_dependencies (job_id); + +; +-- +-- Table: job_dependencies +-- +CREATE TABLE job_dependencies ( + child_job_id bigint NOT NULL, + parent_job_id bigint NOT NULL, + dependency integer NOT NULL, + PRIMARY KEY (child_job_id, parent_job_id, dependency) +); +CREATE INDEX job_dependencies_idx_child_job_id on job_dependencies (child_job_id); +CREATE INDEX job_dependencies_idx_parent_job_id on job_dependencies (parent_job_id); +CREATE INDEX idx_job_dependencies_dependency on job_dependencies (dependency); + +; +-- +-- Table: job_locks +-- +CREATE TABLE job_locks ( + name text NOT NULL, + owner bigint NOT NULL, + locked_by text, + count integer DEFAULT 1 NOT NULL, + PRIMARY KEY (name, owner) +); +CREATE INDEX job_locks_idx_owner on job_locks (owner); + +; +-- +-- Table: job_networks +-- +CREATE TABLE job_networks ( + name text NOT NULL, + job_id bigint NOT NULL, + vlan integer NOT NULL, + PRIMARY KEY (name, job_id) +); +CREATE INDEX job_networks_idx_job_id on job_networks (job_id); + +; +-- +-- Table: jobs_assets +-- +CREATE TABLE jobs_assets ( + job_id bigint NOT NULL, + asset_id bigint NOT NULL, + created_by boolean DEFAULT '0' NOT NULL, + t_created timestamp NOT NULL, + t_updated timestamp NOT NULL, + CONSTRAINT jobs_assets_job_id_asset_id UNIQUE (job_id, asset_id) +); +CREATE INDEX jobs_assets_idx_asset_id on jobs_assets (asset_id); +CREATE INDEX jobs_assets_idx_job_id on jobs_assets (job_id); + +; +-- +-- Table: screenshot_links +-- +CREATE TABLE screenshot_links ( + screenshot_id bigint NOT NULL, + job_id bigint NOT NULL +); +CREATE INDEX screenshot_links_idx_job_id on screenshot_links (job_id); +CREATE INDEX screenshot_links_idx_screenshot_id on screenshot_links (screenshot_id); + +; +-- +-- Foreign Key Definitions +-- + +; +ALTER TABLE job_modules ADD CONSTRAINT job_modules_fk_job_id FOREIGN KEY (job_id) + REFERENCES jobs (id) ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE job_settings ADD CONSTRAINT job_settings_fk_job_id FOREIGN KEY (job_id) + REFERENCES jobs (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE job_template_settings ADD CONSTRAINT job_template_settings_fk_job_template_id FOREIGN KEY (job_template_id) + REFERENCES job_templates (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE machine_settings ADD CONSTRAINT machine_settings_fk_machine_id FOREIGN KEY (machine_id) + REFERENCES machines (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE product_settings ADD CONSTRAINT product_settings_fk_product_id FOREIGN KEY (product_id) + REFERENCES products (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE test_suite_settings ADD CONSTRAINT test_suite_settings_fk_test_suite_id FOREIGN KEY (test_suite_id) + REFERENCES test_suites (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE worker_properties ADD CONSTRAINT worker_properties_fk_worker_id FOREIGN KEY (worker_id) + REFERENCES workers (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE api_keys ADD CONSTRAINT api_keys_fk_user_id FOREIGN KEY (user_id) + REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE audit_events ADD CONSTRAINT audit_events_fk_user_id FOREIGN KEY (user_id) + REFERENCES users (id) DEFERRABLE; + +; +ALTER TABLE comments ADD CONSTRAINT comments_fk_group_id FOREIGN KEY (group_id) + REFERENCES job_groups (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE comments ADD CONSTRAINT comments_fk_job_id FOREIGN KEY (job_id) + REFERENCES jobs (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE comments ADD CONSTRAINT comments_fk_parent_group_id FOREIGN KEY (parent_group_id) + REFERENCES job_group_parents (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE comments ADD CONSTRAINT comments_fk_user_id FOREIGN KEY (user_id) + REFERENCES users (id) DEFERRABLE; + +; +ALTER TABLE job_groups ADD CONSTRAINT job_groups_fk_parent_id FOREIGN KEY (parent_id) + REFERENCES job_group_parents (id) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE workers ADD CONSTRAINT workers_fk_job_id FOREIGN KEY (job_id) + REFERENCES jobs (id) ON DELETE SET NULL DEFERRABLE; + +; +ALTER TABLE needles ADD CONSTRAINT needles_fk_dir_id FOREIGN KEY (dir_id) + REFERENCES needle_dirs (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE needles ADD CONSTRAINT needles_fk_last_matched_module_id FOREIGN KEY (last_matched_module_id) + REFERENCES job_modules (id) ON DELETE SET NULL DEFERRABLE; + +; +ALTER TABLE needles ADD CONSTRAINT needles_fk_last_seen_module_id FOREIGN KEY (last_seen_module_id) + REFERENCES job_modules (id) ON DELETE SET NULL DEFERRABLE; + +; +ALTER TABLE scheduled_products ADD CONSTRAINT scheduled_products_fk_gru_task_id FOREIGN KEY (gru_task_id) + REFERENCES gru_tasks (id) ON DELETE SET NULL DEFERRABLE; + +; +ALTER TABLE scheduled_products ADD CONSTRAINT scheduled_products_fk_user_id FOREIGN KEY (user_id) + REFERENCES users (id) ON DELETE SET NULL DEFERRABLE; + +; +ALTER TABLE job_templates ADD CONSTRAINT job_templates_fk_group_id FOREIGN KEY (group_id) + REFERENCES job_groups (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE job_templates ADD CONSTRAINT job_templates_fk_machine_id FOREIGN KEY (machine_id) + REFERENCES machines (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE job_templates ADD CONSTRAINT job_templates_fk_product_id FOREIGN KEY (product_id) + REFERENCES products (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE job_templates ADD CONSTRAINT job_templates_fk_test_suite_id FOREIGN KEY (test_suite_id) + REFERENCES test_suites (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE jobs ADD CONSTRAINT jobs_fk_assigned_worker_id FOREIGN KEY (assigned_worker_id) + REFERENCES workers (id) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE jobs ADD CONSTRAINT jobs_fk_blocked_by_id FOREIGN KEY (blocked_by_id) + REFERENCES jobs (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE jobs ADD CONSTRAINT jobs_fk_clone_id FOREIGN KEY (clone_id) + REFERENCES jobs (id) ON DELETE SET NULL DEFERRABLE; + +; +ALTER TABLE jobs ADD CONSTRAINT jobs_fk_group_id FOREIGN KEY (group_id) + REFERENCES job_groups (id) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE jobs ADD CONSTRAINT jobs_fk_scheduled_product_id FOREIGN KEY (scheduled_product_id) + REFERENCES scheduled_products (id) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE assets ADD CONSTRAINT assets_fk_last_use_job_id FOREIGN KEY (last_use_job_id) + REFERENCES jobs (id) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE developer_sessions ADD CONSTRAINT developer_sessions_fk_job_id FOREIGN KEY (job_id) + REFERENCES jobs (id) ON DELETE CASCADE DEFERRABLE; + +; +ALTER TABLE developer_sessions ADD CONSTRAINT developer_sessions_fk_user_id FOREIGN KEY (user_id) + REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE gru_dependencies ADD CONSTRAINT gru_dependencies_fk_gru_task_id FOREIGN KEY (gru_task_id) + REFERENCES gru_tasks (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE gru_dependencies ADD CONSTRAINT gru_dependencies_fk_job_id FOREIGN KEY (job_id) + REFERENCES jobs (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE job_dependencies ADD CONSTRAINT job_dependencies_fk_child_job_id FOREIGN KEY (child_job_id) + REFERENCES jobs (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE job_dependencies ADD CONSTRAINT job_dependencies_fk_parent_job_id FOREIGN KEY (parent_job_id) + REFERENCES jobs (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE job_locks ADD CONSTRAINT job_locks_fk_owner FOREIGN KEY (owner) + REFERENCES jobs (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE job_networks ADD CONSTRAINT job_networks_fk_job_id FOREIGN KEY (job_id) + REFERENCES jobs (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE jobs_assets ADD CONSTRAINT jobs_assets_fk_asset_id FOREIGN KEY (asset_id) + REFERENCES assets (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE jobs_assets ADD CONSTRAINT jobs_assets_fk_job_id FOREIGN KEY (job_id) + REFERENCES jobs (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE screenshot_links ADD CONSTRAINT screenshot_links_fk_job_id FOREIGN KEY (job_id) + REFERENCES jobs (id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE; + +; +ALTER TABLE screenshot_links ADD CONSTRAINT screenshot_links_fk_screenshot_id FOREIGN KEY (screenshot_id) + REFERENCES screenshots (id) ON UPDATE CASCADE DEFERRABLE; + +; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/dbicdh/PostgreSQL/upgrade/102-103/001-auto.sql new/openQA-5.1773056733.e071deaf/dbicdh/PostgreSQL/upgrade/102-103/001-auto.sql --- old/openQA-5.1772722702.3877b2ca/dbicdh/PostgreSQL/upgrade/102-103/001-auto.sql 1970-01-01 01:00:00.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/dbicdh/PostgreSQL/upgrade/102-103/001-auto.sql 2026-03-09 12:45:33.000000000 +0100 @@ -0,0 +1,7 @@ +-- +-- Upgrade from 102 to 103 +-- +-- Add deleted_at column to users table for GDPR compliance (user data deletion) +-- +ALTER TABLE users ADD COLUMN deleted_at timestamp; +CREATE INDEX users_idx_deleted_at ON users (deleted_at); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/etc/openqa/openqa.ini new/openQA-5.1773056733.e071deaf/etc/openqa/openqa.ini --- old/openQA-5.1772722702.3877b2ca/etc/openqa/openqa.ini 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/etc/openqa/openqa.ini 2026-03-09 12:45:33.000000000 +0100 @@ -361,11 +361,6 @@ #wait_for_grutask_retries = 6 ## Maximum size of MCP results in bytes (to prevent performance issues) # mcp_max_result_size = 500000 -## To encourage job scenarios with shorter runtime: If MAX_JOB_TIME is greater -## than DEFAULT_MAX_JOB_TIME, MAX_JOB_TIME divided by this number is added to -## the 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 @@ -376,8 +371,13 @@ ## Throttling: set a comma-separated list of test parameters that trigger throttling ## of scheduled openQA jobs by priority. Example: PARAM1:SCALE1[:THRESH1][,...] ## Set a scale that a parameter value, minus an optional threshold, is multiplied by -## and added to each job prio, as: '$base_prio + $scale# * ($param# - $thresh#)' -#prio_throttling_parameters = +## and added to each job prio, as: '$base_prio + $scale# * ($param# - $thresh#)'. +## MAX_JOB_TIME also is configured here: to encourage tests with shorter runtime, +## when MAX_JOB_TIME > DEFAULT_MAX_JOB_TIME, a configured SCALE > 0 is applied; otherwise +## SCALE = 0 (or empty) to disable throttling or < 0 to prioritize long running jobs. +## To configure a custom set of scaling parameters, add also the below default +## rule, e.g. "prio_throttling_parameters = MAX_JOB_TIME:0.007,FOO:3:2000" +#prio_throttling_parameters = MAX_JOB_TIME:0.007 [archiving] ## Moves logs of jobs which are preserved during the cleanup because they are diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/lib/OpenQA/Schema/Result/Users.pm new/openQA-5.1773056733.e071deaf/lib/OpenQA/Schema/Result/Users.pm --- old/openQA-5.1772722702.3877b2ca/lib/OpenQA/Schema/Result/Users.pm 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/lib/OpenQA/Schema/Result/Users.pm 2026-03-09 12:45:33.000000000 +0100 @@ -4,10 +4,11 @@ package OpenQA::Schema::Result::Users; -use Mojo::Base 'DBIx::Class::Core'; +use Mojo::Base 'DBIx::Class::Core', -signatures; use URI::Escape 'uri_escape'; use Digest::MD5 'md5_hex'; +use DateTime; __PACKAGE__->table('users'); __PACKAGE__->load_components(qw(InflateColumn::DateTime Timestamps)); @@ -51,6 +52,10 @@ data_type => 'integer', default_value => 1, }, + deleted_at => { + data_type => 'timestamp', + is_nullable => 1, + }, ); __PACKAGE__->add_timestamps; __PACKAGE__->set_primary_key('id'); @@ -58,6 +63,8 @@ __PACKAGE__->has_many( developer_sessions => 'OpenQA::Schema::Result::DeveloperSessions', 'user_id', {cascade_delete => 1}); +__PACKAGE__->has_many(comments => 'OpenQA::Schema::Result::Comments', 'user_id'); +__PACKAGE__->has_many(audit_events => 'OpenQA::Schema::Result::AuditEvents', 'user_id'); __PACKAGE__->add_unique_constraint([qw(username provider)]); sub name { @@ -84,5 +91,32 @@ } } +sub is_deleted ($self) { defined $self->deleted_at } + +sub anonymize ($self) { + return if $self->is_deleted; + my $user_id = $self->id; + my $schema = $self->result_source->schema; + $schema->txn_do( + sub { + $self->api_keys->delete; + $_->update({event_data => _anonymize_event_data($_->event_data, $self->username)}) + for $self->audit_events->all; + $self->update( + { + username => "deleted-user-$user_id", + email => undef, + fullname => undef, + nickname => undef, + deleted_at => DateTime->now, + }); + }); +} + +sub _anonymize_event_data ($event_data, $username) { + return $event_data unless defined $event_data && defined $username; + my $placeholder = 'deleted-user'; + $event_data =~ s/\Q$username\E/$placeholder/gr; +} 1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/lib/OpenQA/Schema/ResultSet/Jobs.pm new/openQA-5.1773056733.e071deaf/lib/OpenQA/Schema/ResultSet/Jobs.pm --- old/openQA-5.1772722702.3877b2ca/lib/OpenQA/Schema/ResultSet/Jobs.pm 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/lib/OpenQA/Schema/ResultSet/Jobs.pm 2026-03-09 12:45:33.000000000 +0100 @@ -170,41 +170,50 @@ return $job; } -sub _apply_prio_throttling ($settings, $new_job_args) { - 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; - } +sub _update_priority ($value, $throt_config, $job_args) { + my $scale = $throt_config->{scale} // 0; + my $reference = $throt_config->{reference} // 0; + my $prio = int(($value - $reference) * $scale); + $job_args->{priority} += $prio; + return ": $prio, scale: $scale" . ($reference ? ", reference: $reference;" : ';'); +} + +sub _apply_max_job_time_prio ($factor, $time, $throt_config, $job_args) { + my $info = ''; + $factor = (looks_like_number $factor && $factor > 0) ? $factor : 1; + $time = (looks_like_number $time && $time > 0) ? $time : DEFAULT_MAX_JOB_TIME; + $time = (int($time * $factor)); + if ($time > DEFAULT_MAX_JOB_TIME && defined $throt_config) { + $info = 'MAX_JOB_TIME' . _update_priority($time, $throt_config, $job_args); } + return $info; +} +sub _apply_prio_throttling ($settings, $new_job_args) { + my $debug_msg; + my $base_prio = $new_job_args->{priority} // 0; if (my $throttling = OpenQA::App->singleton && OpenQA::App->singleton->config->{misc_limits}->{prio_throttling_data}) { - my $throttling_info; + my $throttling_info = _apply_max_job_time_prio( + $settings->{TIMEOUT_SCALE}, + $settings->{MAX_JOB_TIME}, + $throttling->{MAX_JOB_TIME}, + $new_job_args + ); for my $resource (keys %$throttling) { - next unless defined $settings->{$resource}; - my $scale = $throttling->{$resource}->{scale}; - my $reference = $throttling->{$resource}->{reference}; - my $prio = int(($settings->{$resource} - $reference) * $scale); - $throttling_info .= "$resource, scale: $scale" . ($reference ? ", reference: $reference;" : ';'); - $new_job_args->{priority} += $prio; + next if (!defined $settings->{$resource} || $resource eq 'MAX_JOB_TIME'); + $throttling_info + .= $resource . _update_priority($settings->{$resource}, $throttling->{$resource}, $new_job_args); } $debug_msg .= sprintf - '. Adjusting job priority by %s based on resource requirement(s): %s', - $new_job_args->{priority}, $throttling_info + '- Adjusting job priority from %d to %d based on resource requirement(s): %s', + $base_prio, $new_job_args->{priority}, $throttling_info if $throttling_info; } return $debug_msg; } - sub _handle_dependency_settings ($self, $settings, $new_job_args) { my $job_settings = $self->result_source->schema->resultset('JobSettings'); # handle dependencies diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/lib/OpenQA/Schema.pm new/openQA-5.1773056733.e071deaf/lib/OpenQA/Schema.pm --- old/openQA-5.1772722702.3877b2ca/lib/OpenQA/Schema.pm 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/lib/OpenQA/Schema.pm 2026-03-09 12:45:33.000000000 +0100 @@ -22,7 +22,7 @@ # after bumping the version please look at the instructions in the docs/Contributing.asciidoc file # on what scripts should be run and how -our $VERSION = $ENV{OPENQA_SCHEMA_VERSION_OVERRIDE} // 102; +our $VERSION = $ENV{OPENQA_SCHEMA_VERSION_OVERRIDE} // 103; __PACKAGE__->load_namespaces; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/lib/OpenQA/Setup.pm new/openQA-5.1773056733.e071deaf/lib/OpenQA/Setup.pm --- old/openQA-5.1772722702.3877b2ca/lib/OpenQA/Setup.pm 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/lib/OpenQA/Setup.pm 2026-03-09 12:45:33.000000000 +0100 @@ -257,9 +257,8 @@ wait_for_grutask_retries => 6, # exponential, ~4 minutes worker_limit_retry_delay => ONE_HOUR / 4, mcp_max_result_size => 500000, - max_job_time_prio_scale => 100, scheduled_product_min_storage_duration => 34, - prio_throttling_parameters => '', + prio_throttling_parameters => 'MAX_JOB_TIME:0.007', prio_throttling_data => undef, }, archiving => { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/lib/OpenQA/WebAPI/Controller/API/V1/User.pm new/openQA-5.1773056733.e071deaf/lib/OpenQA/WebAPI/Controller/API/V1/User.pm --- old/openQA-5.1772722702.3877b2ca/lib/OpenQA/WebAPI/Controller/API/V1/User.pm 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/lib/OpenQA/WebAPI/Controller/API/V1/User.pm 2026-03-09 12:45:33.000000000 +0100 @@ -35,9 +35,30 @@ sub delete ($self) { my $user = $self->schema->resultset('Users')->find($self->param('id')); return $self->render(json => {error => 'Not found'}, status => 404) unless $user; - my $result = $user->delete(); - $self->emit_event('openqa_user_deleted', {username => $user->username}); - $self->render(json => {result => $result}); + my $username = $user->username; + $user->anonymize; + $self->emit_event('openqa_user_deleted', {username => $username}); + $self->render(json => {result => 1}); +} + +=over 4 + +=item delete_self() + +Delete the current user's own account (anonymize all data). + +=back + +=cut + +sub delete_self ($self) { + my $user = $self->current_user; + return $self->render(json => {error => 'Not found'}, status => 404) unless $user; + return $self->render(json => {error => 'User already deleted'}, status => 400) if $user->is_deleted; + my $username = $user->username; + $user->anonymize; + $self->emit_event('openqa_user_deleted', {username => $username}); + $self->render(json => {result => 1}); } =over 4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/lib/OpenQA/WebAPI/Controller/Test.pm new/openQA-5.1773056733.e071deaf/lib/OpenQA/WebAPI/Controller/Test.pm --- old/openQA-5.1772722702.3877b2ca/lib/OpenQA/WebAPI/Controller/Test.pm 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/lib/OpenQA/WebAPI/Controller/Test.pm 2026-03-09 12:45:33.000000000 +0100 @@ -994,7 +994,7 @@ sub _get_latest_job ($self) { my %search_args = (limit => 1); - for my $arg (OpenQA::Schema::Result::Jobs::SCENARIO_WITH_MACHINE_KEYS) { + for my $arg (OpenQA::Schema::Result::Jobs::SCENARIO_WITH_MACHINE_KEYS, keys %{META_MAPPING()}) { my $key = lc $arg; next unless defined $self->param($key); $search_args{$key} = $self->param($key); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/lib/OpenQA/WebAPI.pm new/openQA-5.1773056733.e071deaf/lib/OpenQA/WebAPI.pm --- old/openQA-5.1772722702.3877b2ca/lib/OpenQA/WebAPI.pm 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/lib/OpenQA/WebAPI.pm 2026-03-09 12:45:33.000000000 +0100 @@ -473,6 +473,7 @@ $api_ru->get('/users/me/api_keys')->name('apiv1_list_user_api_keys')->to('user#list_api_keys'); $api_ru->post('/users/me/api_keys')->name('apiv1_create_user_api_key')->to('user#create_api_key'); $api_ru->delete('/users/me/api_keys/<key>')->name('apiv1_delete_user_api_key')->to('user#delete_api_key'); + $api_ru->delete('/users/me')->name('apiv1_delete_current_user')->to('user#delete_self'); # api/v1/search $api_public_r->get('/experimental/search')->name('apiv1_search_query')->to('search#query'); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/t/10-overview_badge.t new/openQA-5.1773056733.e071deaf/t/10-overview_badge.t --- old/openQA-5.1772722702.3877b2ca/t/10-overview_badge.t 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/t/10-overview_badge.t 2026-03-09 12:45:33.000000000 +0100 @@ -14,6 +14,7 @@ my $test_case = OpenQA::Test::Case->new; $test_case->init_data(fixtures_glob => '01-jobs.pl 05-job_modules.pl'); my $t = Test::Mojo->new('OpenQA::WebAPI'); +my $jobs = $t->app->schema->resultset('Jobs'); $t->get_ok('/tests/overview/badge')->status_is(200)->content_type_is('image/svg+xml')->content_like(qr/running/); $t->get_ok('/tests/overview/badge?result=passed')->status_is(200)->content_like(qr/passed/); @@ -31,10 +32,14 @@ ->header_is('Cache-Control' => 'max-age=0, no-cache')->element_exists('svg', 'valid svg badge'); $t->get_ok('/tests/9992711111/badge')->status_is(404)->content_type_is('image/svg+xml')->element_exists('svg') ->content_like(qr/404/, 'valid 404 svg badge'); - $t->get_ok('/tests/latest/badge?test=kde&machine=32bit')->status_is(200)->content_type_is('image/svg+xml') - ->element_exists('svg', 'valid latest svg badge'); - $t->app->schema->resultset('Jobs')->find(99928) - ->update({state => SCHEDULED, result => NONE, blocked_by_id => 99927}); + $t->get_ok('/tests/latest/badge?test=kde&machine=64bit')->status_is(200)->content_type_is('image/svg+xml') + ->element_exists('svg', 'valid latest svg badge')->content_like(qr/running/); + $t->get_ok('/tests/latest/badge?test=kde&machine=64bit&result=passed')->status_is(404); + $jobs->find(99963)->update({result => FAILED, state => DONE}); + $jobs->find(99962)->update({result => PASSED, state => DONE}); + $t->get_ok('/tests/latest/badge?test=kde&machine=64bit&result=passed')->status_is(200)->content_like(qr/passed/); + $t->get_ok('/tests/latest/badge?test=kde&machine=64bit')->status_is(200)->content_like(qr/failed/); + $jobs->find(99928)->update({state => SCHEDULED, result => NONE, blocked_by_id => 99927}); $t->get_ok('/tests/99928/badge')->status_is(200)->content_type_is('image/svg+xml')->element_exists('svg') ->content_like(qr/blocked/, 'valid blocked svg badge'); }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/t/api/04-jobs.t new/openQA-5.1773056733.e071deaf/t/api/04-jobs.t --- old/openQA-5.1772722702.3877b2ca/t/api/04-jobs.t 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/t/api/04-jobs.t 2026-03-09 12:45:33.000000000 +0100 @@ -17,7 +17,7 @@ use OpenQA::Test::TimeLimit '30'; use OpenQA::Test::Utils 'mock_io_loop'; use OpenQA::App; -use OpenQA::Constants 'WORKER_COMMAND_CANCEL'; +use OpenQA::Constants qw(WORKER_COMMAND_CANCEL DEFAULT_MAX_JOB_TIME); use OpenQA::Events; use OpenQA::File; use OpenQA::Parser 'parser'; @@ -1029,43 +1029,84 @@ subtest 'priority correctly assigned when posting job' => sub { my $default_prio = 50; - # post new job and check default priority $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/group', 'opensuse', 'group assigned (1)'); $t->json_is('/job/priority', $default_prio, 'global default priority assigned'); - subtest 'priority malus due to high MAX_JOB_TIME' => sub { - my %new_job_args = (priority => $default_prio); + subtest 'priority scaling due to high MAX_JOB_TIME-1' => sub { my $max_time = 7300; + my $add = 51; local $jobs_post_params{MAX_JOB_TIME} = $max_time; $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', $default_prio + $max_time / 100, 'increased prio value'); + $t->json_is('/job/priority', $default_prio + $add, 'post /api: increased prio value'); - local $jobs_post_params{TIMEOUT_SCALE} = 2; + my %new_job_args = (priority => $default_prio); + $add = 0; + local $jobs_post_params{MAX_JOB_TIME} = DEFAULT_MAX_JOB_TIME; + $t->app->config->{misc_limits}->{prio_throttling_parameters} = 'MAX_JOB_TIME:1000'; + my $config = OpenQA::Setup::read_config($t->app); + $config->{misc_limits}->{prio_throttling_data} = OpenQA::Setup::_load_prio_throttling($t->app, $config); OpenQA::Schema::ResultSet::Jobs::_apply_prio_throttling(\%jobs_post_params, \%new_job_args); - is $new_job_args{priority}, $default_prio + $max_time * 2 / 100, 'increased prio value with TIMEOUT_SCALE'; + is $new_job_args{priority}, $default_prio + $add, 'no change to prio for max_job_time = DEFAULT'; + + %new_job_args = (priority => $default_prio); + $add = 118; + local $jobs_post_params{MAX_JOB_TIME} = DEFAULT_MAX_JOB_TIME; + local $jobs_post_params{TIMEOUT_SCALE} = 3; + $t->app->config->{misc_limits}->{prio_throttling_parameters} = 'MAX_JOB_TIME:0.0055,YYY:0.01:100000'; + $config = OpenQA::Setup::read_config($t->app); + $config->{misc_limits}->{prio_throttling_data} = OpenQA::Setup::_load_prio_throttling($t->app, $config); + OpenQA::Schema::ResultSet::Jobs::_apply_prio_throttling(\%jobs_post_params, \%new_job_args); + is $new_job_args{priority}, $default_prio + $add, 'increased prio for value max_job_time * TIMEOUT_SCALE'; delete $jobs_post_params{TIMEOUT_SCALE}; - my $limits = OpenQA::App->singleton->config->{misc_limits}; %new_job_args = (priority => $default_prio); - $limits->{max_job_time_prio_scale} = 10; + $add = 0; + local $jobs_post_params{MAX_JOB_TIME} = 1800; + $config = OpenQA::Setup::read_config($t->app); + $config->{misc_limits}->{prio_throttling_parameters} = 'MAX_JOB_TIME:0.005'; + $config->{misc_limits}->{prio_throttling_data} = OpenQA::Setup::_load_prio_throttling($t->app, $config); + OpenQA::Schema::ResultSet::Jobs::_apply_prio_throttling(\%jobs_post_params, \%new_job_args); + is $new_job_args{priority}, $default_prio + $add, 'no change to prio for max_job_time < DEFAULT'; + + %new_job_args = (priority => $default_prio); + $add = 0; + local $jobs_post_params{MAX_JOB_TIME} = 11000; + $config = OpenQA::Setup::read_config($t->app); + $config->{misc_limits}->{prio_throttling_parameters} = 'FAKE_MAX_JOB_TIME:10:100000'; + $config->{misc_limits}->{prio_throttling_data} = OpenQA::Setup::_load_prio_throttling($t->app, $config); OpenQA::Schema::ResultSet::Jobs::_apply_prio_throttling(\%jobs_post_params, \%new_job_args); - is $new_job_args{priority}, $default_prio + $max_time / 10, 'custom scale value: increased prio value'; + is $new_job_args{priority}, $default_prio + $add, + 'prio value remained unchanged if MAX_JOB_TIME is not configured'; %new_job_args = (priority => $default_prio); - $limits->{max_job_time_prio_scale} = 0; + $add = 0; + local $jobs_post_params{MAX_JOB_TIME} = 11000; + $config = OpenQA::Setup::read_config($t->app); + $config->{misc_limits}->{prio_throttling_parameters} = 'MAX_JOB_TIME::100000'; + $config->{misc_limits}->{prio_throttling_data} = OpenQA::Setup::_load_prio_throttling($t->app, $config); OpenQA::Schema::ResultSet::Jobs::_apply_prio_throttling(\%jobs_post_params, \%new_job_args); - is $new_job_args{priority}, $default_prio, 'feature disabled: prio value unchanged'; + is $new_job_args{priority}, $default_prio + $add, + 'prio value unchanged being scale factor missing in configuration'; + + %new_job_args = (priority => $default_prio); + $add = 51 + 20; + local $jobs_post_params{MAX_JOB_TIME} = DEFAULT_MAX_JOB_TIME + 100; + local $jobs_post_params{QEMURAM} = 4096; + $config = OpenQA::Setup::read_config($t->app); + $config->{misc_limits}->{prio_throttling_parameters} = 'QEMURAM:0.01:2048, MAX_JOB_TIME:0.007'; + $config->{misc_limits}->{prio_throttling_data} = OpenQA::Setup::_load_prio_throttling($t->app, $config); + OpenQA::Schema::ResultSet::Jobs::_apply_prio_throttling(\%jobs_post_params, \%new_job_args); + is $new_job_args{priority}, $default_prio + $add, 'increased prio due to high MAX_JOB_TIME and QEMURAM'; }; subtest 'priority scaled up due to QEMURAM demand' => sub { my %new_job_args = (priority => $default_prio); my $add = 20; local $jobs_post_params{QEMURAM} = 4096; - my $config = OpenQA::Setup::read_config($t->app); $config->{misc_limits}->{prio_throttling_parameters} = 'XXX :0.2, QEMURAM:0.01:2048'; $config->{misc_limits}->{prio_throttling_data} = OpenQA::Setup::_load_prio_throttling($t->app, $config); @@ -1077,7 +1118,6 @@ my %new_job_args = (priority => $default_prio); my $add = -10; local $jobs_post_params{QEMURAM} = 1024; - my $config = OpenQA::Setup::read_config($t->app); $config->{misc_limits}->{prio_throttling_parameters} = 'XXX :0.2, QEMURAM:0.01:2048'; $config->{misc_limits}->{prio_throttling_data} = OpenQA::Setup::_load_prio_throttling($t->app, $config); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/t/api/15-users.t new/openQA-5.1773056733.e071deaf/t/api/15-users.t --- old/openQA-5.1772722702.3877b2ca/t/api/15-users.t 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/t/api/15-users.t 2026-03-09 12:45:33.000000000 +0100 @@ -13,7 +13,7 @@ use OpenQA::Client; use OpenQA::Test::Case; -OpenQA::Test::Case->new->init_data(fixtures_glob => '03-users.pl'); +OpenQA::Test::Case->new->init_data(fixtures_glob => '0{1-jobs,3-users}.pl'); my $t = Test::Mojo->new('OpenQA::WebAPI'); my $app = $t->app; @@ -80,4 +80,44 @@ $t->delete_ok("/api/v1/users/me/api_keys/NONEXISTENT")->status_is(404, 'delete nonexistent key'); }; +subtest 'test delete_self' => sub { + my $user = $app->schema->resultset('Users')->find(99902); + ok $user, 'user exists before deletion'; + ok $user->api_keys->create({t_expiration => DateTime->now->add(years => 1)}); + ok $user->comments->create({text => 'test comment'}), 'comment created'; + ok $user->developer_sessions->create({job_id => 80000}), 'developer session created'; + $t->delete_ok('/api/v1/users/me')->status_is(200, 'user can delete own account'); + $user->discard_changes; + ok $user->is_deleted, 'user is marked as deleted'; + is $user->username, 'deleted-user-99902', 'username anonymized'; + is $user->email, undef, 'email cleared'; + is $user->fullname, undef, 'fullname cleared'; + is $user->nickname, undef, 'nickname cleared'; + my $keys_after = $app->schema->resultset('ApiKeys')->search({user_id => 99902})->count; + is $keys_after, 0, 'API keys deleted'; + is $user->comments->count, 1, 'comment preserved'; + is $user->developer_sessions->count, 1, 'developer session preserved'; + my $event = OpenQA::Test::Case::find_most_recent_event($app->schema, 'user_deleted'); + is $event->{username}, 'https://openid.camelot.uk/lancelot', 'delete event has original username'; +}; + +subtest 'anonymize audit event data' => sub { + my $user = $app->schema->resultset('Users')->find(99903); + ok $user, 'user 99903 (percival) exists'; + my $username = $user->username; + my $event_data = "User $username performed an action"; + $user->audit_events->create( + { + event => 'job_created', + event_data => $event_data, + }); + my $audit_event = $user->audit_events->find({event => 'job_created'}); + is $audit_event->event_data, $event_data, 'audit event created with username in event_data'; + $user->anonymize; + $user->discard_changes; + $audit_event->discard_changes; + is $audit_event->event_data, "User deleted-user performed an action", 'event_data anonymized'; + is $audit_event->user_id, 99903, 'audit event association preserved'; +}; + done_testing(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/t/config.t new/openQA-5.1773056733.e071deaf/t/config.t --- old/openQA-5.1772722702.3877b2ca/t/config.t 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/t/config.t 2026-03-09 12:45:33.000000000 +0100 @@ -193,9 +193,8 @@ wait_for_grutask_retries => 6, worker_limit_retry_delay => ONE_HOUR / 4, mcp_max_result_size => 500000, - max_job_time_prio_scale => 100, scheduled_product_min_storage_duration => 34, - prio_throttling_parameters => '', + prio_throttling_parameters => 'MAX_JOB_TIME:0.007', }, archiving => { archive_preserved_important_jobs => 0, @@ -218,7 +217,7 @@ $test_config->{_openid_secret} = $config->{_openid_secret}; $test_config->{logging}->{level} = 'debug'; $test_config->{global}->{service_port_delta} = 2; - $test_config->{misc_limits}->{prio_throttling_data} = undef; + $test_config->{misc_limits}->{prio_throttling_data} = {MAX_JOB_TIME => {scale => '0.007', reference => 0}}; is ref delete $config->{global}->{auto_clone_regex}, 'Regexp', 'auto_clone_regex parsed as regex'; ok delete $config->{'test_preset example'}, 'default values for example tests assigned'; is_deeply $config, $test_config, '"test" configuration'; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/t/ui/18-tests-details.t new/openQA-5.1773056733.e071deaf/t/ui/18-tests-details.t --- old/openQA-5.1772722702.3877b2ca/t/ui/18-tests-details.t 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/t/ui/18-tests-details.t 2026-03-09 12:45:33.000000000 +0100 @@ -643,6 +643,16 @@ like $build_href, qr/groupid=1001/, 'href to test overview'; like $build_href, qr/version=13.1/, 'href to test overview'; like $build_href, qr/build=0091/, 'href to test overview'; + $jobs->find(99963)->update({result => FAILED, state => DONE}); + $jobs->find(99962)->update({result => PASSED, state => DONE}); + $t->get_ok('/tests/latest?test=kde&machine=64bit')->status_is(200); + is $t->tx->res->dom->at('#info_box .card-header a')->{href}, '/tests/99963', 'returns latest job'; + $t->get_ok('/tests/latest?test=kde&machine=64bit&result=failed')->status_is(200); + is $t->tx->res->dom->at('#info_box .card-header a')->{href}, '/tests/99963', 'returns latest failed job'; + $t->get_ok('/tests/latest?test=kde&machine=64bit&result=passed')->status_is(200); + is $t->tx->res->dom->at('#info_box .card-header a')->{href}, '/tests/99962', + 'returns latest passed job (skipping newer failed one)'; + $t->get_ok('/tests/latest?test=kde&machine=64bit&result=parallel_failed')->status_is(404); $t->get_ok('/tests/latest?test=foobar')->status_is(404); }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1772722702.3877b2ca/templates/webapi/api_key/index.html.ep new/openQA-5.1773056733.e071deaf/templates/webapi/api_key/index.html.ep --- old/openQA-5.1772722702.3877b2ca/templates/webapi/api_key/index.html.ep 2026-03-05 15:58:22.000000000 +0100 +++ new/openQA-5.1773056733.e071deaf/templates/webapi/api_key/index.html.ep 2026-03-09 12:45:33.000000000 +0100 @@ -1,5 +1,8 @@ % layout 'bootstrap'; % title 'API Keys'; +% content_for 'ready_function' => begin + setup_delete_account(); +% end <div> <h2><%= title %></h2> %= include 'layouts/info' @@ -39,4 +42,50 @@ % } </tbody> </table> + + <div class="card mt-4 border-danger"> + <div class="card-header bg-danger text-white">Delete My Account</div> + <div class="card-body"> + <p>Deleting your account will:</p> + <ul> + <li>Remove all your API keys</li> + <li>Anonymize your comments (your username will be removed)</li> + <li>Anonymize audit log entries (your username will be replaced)</li> + <li>Replace your username, email, and profile information with placeholder values</li> + </ul> + <div class="alert alert-warning"> + <strong>This action cannot be undone.</strong> + </div> + <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteAccountModal"> + Delete My Account + </button> + </div> + </div> + + <div class="modal fade" id="deleteAccountModal" tabindex="-1"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title">Confirm Account Deletion</h5> + <button type="button" class="btn-close" data-bs-dismiss="modal"></button> + </div> + <div class="modal-body"> + <p>Are you sure you want to delete your account? This action cannot be undone.</p> + <div class="mb-3"> + <label class="form-label" for="confirm-delete">Type your username to confirm:</label> + % my $user = (ref($current_user) // '') eq 'OpenQA::Schema::Result::Users' ? $current_user : ($current_user->{user} // ''); + <input type="text" class="form-control" id="confirm-delete" data-expected="<%= $user && $user->can('name') ? $user->name : '' %>"> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> + <button type="button" class="btn btn-danger" id="confirm-delete-btn" disabled + data-delete-url="<%= url_for('apiv1_delete_current_user') %>" + data-redirect-url="<%= url_for('index') %>"> + Delete My Account + </button> + </div> + </div> + </div> + </div> </div> ++++++ openQA.obsinfo ++++++ --- /var/tmp/diff_new_pack.zfvx9V/_old 2026-03-09 16:33:29.895566336 +0100 +++ /var/tmp/diff_new_pack.zfvx9V/_new 2026-03-09 16:33:29.903566662 +0100 @@ -1,5 +1,5 @@ name: openQA -version: 5.1772722702.3877b2ca -mtime: 1772722702 -commit: 3877b2caf194913eca23a443821a36f48498b8f5 +version: 5.1773056733.e071deaf +mtime: 1773056733 +commit: e071deaf70856b3cb629e753cbb7f864afbfb37d
