-
Notifications
You must be signed in to change notification settings - Fork 3.2k
[#73373] Background job for migrating pre-existing data to semantic identifiers #22566
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 123 commits
334b85e
c2566fc
cf7a41d
fa179c1
490c90e
4a7cedf
471f2f5
b9dccaa
b32d601
711dc65
4c2631c
42887b4
39587d5
3b52f25
a0cd7ea
e941542
595496c
15a477a
de16856
47d6972
4460fc4
d5f02b8
9120f9d
f3fc8a7
306443a
ed304e4
8830fd8
1c4aefc
1145211
8edd09d
62ea269
30a752f
26a890a
25962e5
ba9cfd2
5d143c3
7f21633
6be9cb8
4752193
24f3012
b4baa10
9f65fb7
342e744
c68f43f
6fa6b36
a3930e3
1772030
a14f008
3117589
aa8d6b5
aad767e
87f4c90
7955c21
d91920b
ec086c7
6532170
8ab410d
7f47303
50b56a0
b419054
48b8d16
aa4c910
17559b3
9548b0f
209b0c4
65790dd
6dc7474
2ac5a4c
fcfbceb
a6fc410
3693926
79b9997
7343662
59f64a8
fb64770
e9cf0c1
1cdef00
af1e613
20c6d37
8216a62
bab0b30
314bc36
ded6728
e11d5fe
c7f954b
4acbb83
6307392
1ba50ee
98f1e20
df05930
8e877b8
0e99e46
53c00f9
4939b5a
5671e11
de99339
94e79c8
3529fa1
d92529b
aa5af20
74dcdd7
99d12fd
90f3c5f
a50e9c0
50646af
851b551
52fce11
6931dbc
4359313
4179141
5422b14
45ddcfc
b278845
cb1d926
b6884b0
7c5508c
aa3eb23
5cb7162
ff68d81
2116fc7
483bc89
f998398
c52f096
7710554
8eb3662
3e3d17f
7ad067f
1c0d5d7
7520e92
ba53230
60e63e8
a7d6f86
85dac65
e684e8f
d01c444
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| #-- copyright | ||
| # OpenProject is an open source project management software. | ||
| # Copyright (C) the OpenProject GmbH | ||
| # | ||
| # This program is free software; you can redistribute it and/or | ||
| # modify it under the terms of the GNU General Public License version 3. | ||
| # | ||
| # OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: | ||
| # Copyright (C) 2006-2013 Jean-Philippe Lang | ||
| # Copyright (C) 2010-2013 the ChiliProject Team | ||
| # | ||
| # This program is free software; you can redistribute it and/or | ||
| # modify it under the terms of the GNU General Public License | ||
| # as published by the Free Software Foundation; either version 2 | ||
| # of the License, or (at your option) any later version. | ||
| # | ||
| # This program is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU General Public License for more details. | ||
| # | ||
| # You should have received a copy of the GNU General Public License | ||
| # along with this program; if not, write to the Free Software | ||
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
| # | ||
| # See COPYRIGHT and LICENSE files for more details. | ||
| #++ | ||
|
|
||
| module ProjectIdentifiers | ||
| # Brings a single project fully up to date for semantic identifier mode: | ||
| # | ||
| # 1. Fixes the project identifier if it is not in valid semantic format. | ||
| # 2. Syncs the sequence counter so it is >= any existing sequence_number | ||
| # (guards against counter underflow caused by cross-project WP moves). | ||
| # 3. Rewrites stale WP identifiers whose prefix no longer matches the project. | ||
| # 4. Assigns sequence numbers to WPs that have none yet. | ||
| # 5. Seeds the alias table for all historical project identifier prefixes. | ||
| class BackfillProjectService | ||
| def initialize(project) | ||
| @project = project | ||
| end | ||
|
|
||
| def call | ||
| fix_identifier_if_needed | ||
| reset_stale_identifiers | ||
| backfill_missing_ids | ||
| seed_alias_table | ||
| end | ||
|
|
||
| private | ||
|
|
||
| attr_reader :project | ||
|
|
||
| def fix_identifier_if_needed | ||
| detector = WorkPackages::IdentifierAutofix::ProblematicIdentifiers.new | ||
| generator = WorkPackages::IdentifierAutofix::ProjectIdentifierSuggestionGenerator | ||
| # Pure format check — no DB queries. nil means the identifier is fine. | ||
| return unless detector.format_error_reason(project.identifier) | ||
|
|
||
| # Prefer restoring the project's last known semantic identifier (from FriendlyId history) | ||
| # so that existing WP identifiers remain valid and aliases need no update. | ||
| # Fall back to generating a fresh suggestion if no usable prior slug exists. | ||
| # Two concurrent jobs may occasionally suggest the same identifier, but the | ||
| # unique constraint on projects.identifier will reject the second writer, and | ||
| # the job can be retried. | ||
| new_identifier = project.previous_semantic_identifier || | ||
| generator.suggest_identifier(project.name, exclude: detector.exclusion_set) | ||
|
|
||
| project.identifier = new_identifier | ||
| project.save!(validate: false) | ||
thykel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| end | ||
|
|
||
|
|
||
thykel marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| def reset_stale_identifiers | ||
|
Check notice on line 76 in app/services/project_identifiers/backfill_project_service.rb
|
||
| # Fix WPs that contain identifier that doesn't match the current project prefix | ||
| # (caused by renames or cross-project moves in classic mode) | ||
| WorkPackage | ||
| .where(project:) | ||
| .where.not(sequence_number: nil) | ||
| .where("identifier NOT LIKE ?", "#{project.identifier}-%") | ||
| .find_each { |wp| wp.update_columns(identifier: nil, sequence_number: nil) } | ||
thykel marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| end | ||
|
|
||
| def backfill_missing_ids | ||
| WorkPackage.where(project:, sequence_number: nil).order(:id).find_each do |wp| | ||
| seq, identifier = project.allocate_wp_semantic_identifier! | ||
| wp.update_columns(sequence_number: seq, identifier:) | ||
| end | ||
| end | ||
|
|
||
| def seed_alias_table | ||
| slug_prefixes = project.slugs.pluck(:slug) | ||
| return if slug_prefixes.empty? | ||
|
|
||
| WorkPackage.where(project:).where.not(sequence_number: nil).in_batches do |batch| | ||
| alias_rows = batch.pluck(:id, :sequence_number) | ||
| .product(slug_prefixes) | ||
| .map { |(wp_id, seq), prefix| { identifier: "#{prefix}-#{seq}", work_package_id: wp_id } } | ||
| WorkPackageSemanticAlias.upsert_rows(alias_rows) | ||
| end | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| #-- copyright | ||
| # OpenProject is an open source project management software. | ||
| # Copyright (C) the OpenProject GmbH | ||
| # | ||
| # This program is free software; you can redistribute it and/or | ||
| # modify it under the terms of the GNU General Public License version 3. | ||
| # | ||
| # OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: | ||
| # Copyright (C) 2006-2013 Jean-Philippe Lang | ||
| # Copyright (C) 2010-2013 the ChiliProject Team | ||
| # | ||
| # This program is free software; you can redistribute it and/or | ||
| # modify it under the terms of the GNU General Public License | ||
| # as published by the Free Software Foundation; either version 2 | ||
| # of the License, or (at your option) any later version. | ||
| # | ||
| # This program is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU General Public License for more details. | ||
| # | ||
| # You should have received a copy of the GNU General Public License | ||
| # along with this program; if not, write to the Free Software | ||
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
| # | ||
| # See COPYRIGHT and LICENSE files for more details. | ||
| #++ | ||
|
|
||
| module ProjectIdentifiers | ||
| # Returns the set of project IDs that still need backfilling before the | ||
| # instance can be switched to semantic identifier mode. Three buckets: | ||
| # | ||
| # * projects whose identifier is not in valid semantic format | ||
| # * projects that have work packages with no sequence_number yet | ||
| # * projects that have work packages whose identifier doesn't match | ||
| # the current project prefix (stale due to renames or cross-project moves) | ||
| class PendingProjectsFinder | ||
| def project_ids | ||
| projects_with_bad_identifier | projects_with_unsequenced_wps | projects_with_stale_wps | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def projects_with_bad_identifier | ||
| WorkPackages::IdentifierAutofix::ProblematicIdentifiers.new.scope.ids.to_set | ||
| end | ||
|
|
||
| def projects_with_unsequenced_wps | ||
| WorkPackage.where(sequence_number: nil).distinct.pluck(:project_id).to_set | ||
| end | ||
|
|
||
| def projects_with_stale_wps | ||
| WorkPackage | ||
| .joins(:project) | ||
| .where.not(sequence_number: nil) | ||
| .where("work_packages.identifier IS DISTINCT FROM " \ | ||
| "projects.identifier || '-' || work_packages.sequence_number::text") | ||
| .distinct.pluck(:project_id).to_set | ||
| end | ||
| end | ||
| end |
Uh oh!
There was an error while loading. Please reload this page.