From c913e9ea4d28261e4b1e01afe92cccce6fb3ae43 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Fri, 4 Sep 2026 11:26:51 +0300 Subject: [PATCH 1/2] Include version changes in the "Only changes" activity filter The filter never consulted the work_package_version_journals snapshot table, so a journal whose only change was a target or observed-in version was hidden. --- .../paginator/journal_changes_filter.rb | 10 ++ .../activities_tab/paginator_spec.rb | 115 ++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/app/services/work_packages/activities_tab/paginator/journal_changes_filter.rb b/app/services/work_packages/activities_tab/paginator/journal_changes_filter.rb index e8cc333ac87e..43fd72bb0389 100644 --- a/app/services/work_packages/activities_tab/paginator/journal_changes_filter.rb +++ b/app/services/work_packages/activities_tab/paginator/journal_changes_filter.rb @@ -35,6 +35,7 @@ # * Attachment changes (compares attachable_journals with predecessor) # * Custom field changes (compares customizable_journals with predecessor) # * File link changes (compares storages_file_links_journals with predecessor) +# * Target/observed-in version changes (compares work_package_version_journals with predecessor) # * Cause metadata (system-triggered changes) # * Attribute/data changes (compares work_package_journals columns with immediate predecessor) # @@ -86,6 +87,7 @@ def changes_condition_sql OR EXISTS (#{attachment_changes_condition_sql}) OR EXISTS (#{custom_field_changes_condition_sql}) OR EXISTS (#{file_link_changes_condition_sql}) + OR EXISTS (#{version_changes_condition_sql}) SQL end @@ -127,6 +129,14 @@ def file_link_changes_condition_sql ) end + def version_changes_condition_sql + association_changes_condition_sql( + table: Journal::WorkPackageVersionJournal.table_name, + join_columns: %w[version_id kind], + value_columns: [] + ) + end + def data_changes_condition_sql data_change_columns = Journal::WorkPackageJournal.column_names - ["id"] diff --git a/spec/services/work_packages/activities_tab/paginator_spec.rb b/spec/services/work_packages/activities_tab/paginator_spec.rb index b389cbfe87e8..501e8b279bc3 100644 --- a/spec/services/work_packages/activities_tab/paginator_spec.rb +++ b/spec/services/work_packages/activities_tab/paginator_spec.rb @@ -825,6 +825,121 @@ expect(records.map(&:id)).not_to include(journal_with_file_link_snapshot.id) end end + + context "with target version changes" do + let!(:journal_with_target_version_added) do + version = create(:version, project:) + work_package.target_version_ids_replacements = [version.id] + work_package.save! + work_package.journals.order(:version).last + end + + let!(:journal_with_target_version_snapshot) do + work_package.add_journal(notes: "Unrelated change") + work_package.save! + work_package.journals.order(:version).last + end + + let!(:journal_with_target_version_removed) do + work_package.target_version_ids_replacements = [] + work_package.save! + work_package.journals.order(:version).last + end + + it "includes journal where target version was added" do + _pagy, records = paginator.call + expect(records.map(&:id)).to include(journal_with_target_version_added.id) + + changes = journal_with_target_version_added.reload.get_changes + expect(changes).to have_key("target_versions") + end + + it "excludes journal with only target version snapshot" do + _pagy, records = paginator.call + + expect(journal_with_target_version_snapshot.reload.work_package_version_journals.count).to eq(1) + changes = journal_with_target_version_snapshot.reload.get_changes + expect(changes).to eq({}) # no changes + + expect(records.map(&:id)).not_to include(journal_with_target_version_snapshot.id) + end + + it "includes journal where target version was removed" do + _pagy, records = paginator.call + expect(records.map(&:id)).to include(journal_with_target_version_removed.id) + + changes = journal_with_target_version_removed.reload.get_changes + expect(changes).to have_key("target_versions") + end + end + + context "with observed in version changes" do + let!(:journal_with_observed_in_version_added) do + version = create(:version, project:) + work_package.observed_in_version_ids_replacements = [version.id] + work_package.save! + work_package.journals.order(:version).last + end + + let!(:journal_with_observed_in_version_snapshot) do + work_package.add_journal(notes: "Unrelated change") + work_package.save! + work_package.journals.order(:version).last + end + + let!(:journal_with_observed_in_version_removed) do + work_package.observed_in_version_ids_replacements = [] + work_package.save! + work_package.journals.order(:version).last + end + + it "includes journal where observed in version was added" do + _pagy, records = paginator.call + expect(records.map(&:id)).to include(journal_with_observed_in_version_added.id) + + changes = journal_with_observed_in_version_added.reload.get_changes + expect(changes).to have_key("observed_in_versions") + end + + it "excludes journal with only observed in version snapshot" do + _pagy, records = paginator.call + + expect(journal_with_observed_in_version_snapshot.reload.work_package_version_journals.count).to eq(1) + changes = journal_with_observed_in_version_snapshot.reload.get_changes + expect(changes).to eq({}) # no changes + + expect(records.map(&:id)).not_to include(journal_with_observed_in_version_snapshot.id) + end + + it "includes journal where observed in version was removed" do + _pagy, records = paginator.call + expect(records.map(&:id)).to include(journal_with_observed_in_version_removed.id) + + changes = journal_with_observed_in_version_removed.reload.get_changes + expect(changes).to have_key("observed_in_versions") + end + end + + context "when moving a version from target to observed in" do + let!(:journal_with_version_moved_across_kinds) do + version = create(:version, project:) + work_package.target_version_ids_replacements = [version.id] + work_package.save! + + work_package.target_version_ids_replacements = [] + work_package.observed_in_version_ids_replacements = [version.id] + work_package.save! + work_package.journals.order(:version).last + end + + it "includes the journal even though the moved version's id alone matches the predecessor" do + _pagy, records = paginator.call + expect(records.map(&:id)).to include(journal_with_version_moved_across_kinds.id) + + changes = journal_with_version_moved_across_kinds.reload.get_changes + expect(changes.keys).to include("target_versions", "observed_in_versions") + end + end end context "with :hide_meetings filter" do From 0e9c7be5e53eb4b0843159908b511924abec6d55 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Fri, 4 Sep 2026 15:45:43 +0300 Subject: [PATCH 2/2] Cover multi-value version changes in the "Only changes" filter spec The (version_id, kind) join key exists so a kept sibling version is not mistaken for a change; these examples pin that for target and observed-in versions. --- .../activities_tab/paginator_spec.rb | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/spec/services/work_packages/activities_tab/paginator_spec.rb b/spec/services/work_packages/activities_tab/paginator_spec.rb index 501e8b279bc3..4d46d875d3ef 100644 --- a/spec/services/work_packages/activities_tab/paginator_spec.rb +++ b/spec/services/work_packages/activities_tab/paginator_spec.rb @@ -940,6 +940,85 @@ expect(changes.keys).to include("target_versions", "observed_in_versions") end end + + context "with multi-value version changes" do + let!(:version1) { create(:version, project:) } + let!(:version2) { create(:version, project:) } + let!(:version3) { create(:version, project:) } + + let!(:journal_with_two_target_versions) do + work_package.target_version_ids_replacements = [version1.id, version2.id] + work_package.save! + work_package.journals.order(:version).last + end + + let!(:journal_with_two_target_versions_snapshot) do + work_package.add_journal(notes: "Just a comment") + work_package.save! + work_package.journals.order(:version).last + end + + it "excludes journal with only a multi-value target version snapshot" do + _pagy, records = paginator.call + + expect(journal_with_two_target_versions_snapshot.reload.work_package_version_journals.count).to eq(2) + expect(journal_with_two_target_versions_snapshot.get_changes).to eq({}) + + expect(records.map(&:id)).not_to include(journal_with_two_target_versions_snapshot.id) + end + + shared_examples "a journal with version changes" do |key| + it "is included with #{key} among its changes" do + _pagy, records = paginator.call + + expect(records.map(&:id)).to include(journal.id) + expect(journal.reload.get_changes).to have_key(key) + end + end + + context "when adding a target version to an existing selection" do + let!(:journal) do + work_package.reload.target_version_ids_replacements = [version1.id, version2.id, version3.id] + work_package.save! + work_package.journals.order(:version).last + end + + it_behaves_like "a journal with version changes", "target_versions" + end + + context "when removing one of several target versions" do + let!(:journal) do + work_package.reload.target_version_ids_replacements = [version1.id] + work_package.save! + work_package.journals.order(:version).last + end + + it_behaves_like "a journal with version changes", "target_versions" + end + + context "when swapping one of several target versions" do + let!(:journal) do + work_package.reload.target_version_ids_replacements = [version2.id, version3.id] + work_package.save! + work_package.journals.order(:version).last + end + + it_behaves_like "a journal with version changes", "target_versions" + end + + context "when removing one of several observed in versions" do + let!(:journal) do + work_package.reload.observed_in_version_ids_replacements = [version1.id, version2.id] + work_package.save! + + work_package.observed_in_version_ids_replacements = [version2.id] + work_package.save! + work_package.journals.order(:version).last + end + + it_behaves_like "a journal with version changes", "observed_in_versions" + end + end end context "with :hide_meetings filter" do