Skip to content

Commit

Permalink
Hooks the job to the other job
Browse files Browse the repository at this point in the history
  • Loading branch information
mereghost committed Mar 25, 2024
1 parent f58d8fd commit a714787
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 380 deletions.
6 changes: 3 additions & 3 deletions app/services/projects/copy_service.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2024 the OpenProject GmbH
Expand Down Expand Up @@ -41,9 +43,7 @@ def self.copy_dependencies
::Projects::Copy::QueriesDependentService,
::Projects::Copy::BoardsDependentService,
::Projects::Copy::OverviewDependentService,
::Projects::Copy::StoragesDependentService,
::Projects::Copy::StorageProjectFoldersDependentService,
::Projects::Copy::FileLinksDependentService
::Projects::Copy::StoragesDependentService
]
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ def call(source_location:, destination_name:)
def handle_response(response)
case response
in { status: 202 }
id = extract_id_from_url(response.headers[:location])

ServiceResult.success(result: { id:, url: response.headers[:location] })
ServiceResult.success(result: { id: nil, url: response.headers[:location] })
in { status: 401 }
ServiceResult.failure(result: :unauthorized)
in { status: 403 }
Expand All @@ -78,13 +76,6 @@ def handle_response(response)
end
end

def extract_id_from_url(url)
extractor_regex = /.+\/items\/(?<item_id>\w+)\?/
match_data = extractor_regex.match(url)

match_data[:item_id] if match_data
end

def copy_path_for(source_location)
"/v1.0/drives/#{@storage.drive_id}/items/#{source_location}/copy?@microsoft.graph.conflictBehavior=fail"
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2024 the OpenProject GmbH
Expand Down Expand Up @@ -52,6 +54,11 @@ def copy_dependency(*)
.on_failure { |r| add_error!(source_project_storage.class.to_s, r.to_active_model_errors) }
.result

Storages::CopyProjectFoldersJob.perform_later(source_id: source_project_storage.id,
target_id: project_storage_copy.id,
user_id: User.current.id,
work_package_map: state.work_package_id_lookup)

array << { source: source_project_storage, target: project_storage_copy }
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2024 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 Storages
module FileLinks
class CopyFileLinksService
def self.call(source:, target:, user:, work_packages_map:)
new(source:, target:, user:, work_packages_map:).call
end

def initialize(source:, target:, user:, work_packages_map:)
@source = source
@target = target
@user = user
@work_packages_map = work_packages_map.to_h { |key, value| [key.to_i, value.to_i] }
end

def call
source_file_links = FileLink
.includes(:creator)
.where(container_id: @work_packages_map.keys, container_type: "WorkPackage")

return create_unmanaged_file_links(source_file_links) if @source.project_folder_manual?

create_managed_file_links(source_file_links)
end

private

def create_managed_file_links(source_file_links)
target_files = target_files_map.result
source_files = source_files_info(source_file_links).result

location_map = build_location_map(source_files, target_files)

source_file_links.find_each do |source_link|
attributes = source_link.dup.attributes

attributes.merge!(
'creator_id' => @user.id,
'container_id' => @work_packages_map[source_link.container_id],
'origin_id' => location_map[source_link.origin_id]
)

FileLinks::CreateService.new(user: @user).call(attributes)
end
end

def build_location_map(source_files, target_location_map)
source_location_map = source_files.to_h { |info| [info.id, info.location] }

source_location_map.each_with_object({}) do |(id, location), output|
target = location.gsub(@source.managed_project_folder_path, @target.managed_project_folder_path)

output[id] = target_location_map[target]
end
end

def source_files_info(source_file_links)
Peripherals::Registry
.resolve("#{@source.storage.short_provider_type}.queries.files_info")
.call(storage: @source.storage, user: @user, file_ids: source_file_links.pluck(:origin_id))
end

def target_files_map
Peripherals::Registry
.resolve("#{@source.storage.short_provider_type}.queries.folder_files_file_ids_deep_query")
.call(storage: @source.storage, folder: Peripherals::ParentFolder.new(@target.project_folder_location))
end

def create_unmanaged_file_links(source_file_links)
source_file_links.find_each do |source_file_link|
attributes = source_file_link.dup.attributes

attributes['creator_id'] = @user.id
attributes['container_id'] = @work_packages_map[source_file_link.container_id]

# TODO: Do something when this fails
::Storages::FileLinks::CreateService.new(user: @user).call(attributes)
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -32,44 +32,19 @@ module Storages
module ProjectStorages
class CopyProjectFoldersService
# We might need the User too
def self.call(source_id:, target_id:)
new(source_id, target_id).call
def self.call(source:, target:)
new.call(source, target)
end

def initialize(source_id, target_id)
@source = get_project_storage(source_id)
@target = get_project_storage(target_id)
end

def call
return ServiceResult.success if @source.project_folder_inactive?
return update_target(@source.project_folder_id) if @source.project_folder_manual?

copy_result = copy_project_folder.on_failure { |failed_result| return failed_result }.result

update_target(copy_result[:id]) if copy_result[:id]

ServiceResult.failure(result: copy_result[:url], errors: :polling_required)
end
def call(source, target)
return ServiceResult.success(result: { id: nil }) if source.project_folder_inactive?
return ServiceResult.success(result: { id: source.project_folder_id }) if source.project_folder_manual?

private

def copy_project_folder
Peripherals::Registry
.resolve("#{@source.storage.short_provider_type}.commands.copy_template_folder")
.call(storage: @source.storage,
source_path: @source.project_folder_location,
destination_path: @target.managed_project_folder_path)
end

def update_target(project_folder_id)
ProjectStorages::UpdateService
.new(user: User.system, model: @target)
.call({ project_folder_id:, project_folder_mode: @source.project_folder_mode })
end

def get_project_storage(id)
ProjectStorage.includes(:project, :storage).find(id)
.resolve("#{source.storage.short_provider_type}.commands.copy_template_folder")
.call(storage: source.storage,
source_path: source.project_folder_location,
destination_path: target.managed_project_folder_path)
end
end
end
Expand Down
106 changes: 37 additions & 69 deletions modules/storages/app/workers/storages/copy_project_folders_job.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2024 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 Storages
Expand All @@ -14,91 +39,34 @@ def perform(user_id:, source_id:, target_id:, work_package_map:)
target = ProjectStorage.find(target_id)
source = ProjectStorage.find(source_id)
user = User.find(user_id)
new_work_package_map = work_package_map.transform_keys(&:to_i)

# TODO: Do Something when this fails
project_folder_result = if polling?
results_from_polling
else
initiate_project_folder_copy(source, target)
ProjectStorages::CopyProjectFoldersService
.call(source:, target:)
.on_success { |success| prepare_polling(success.result) }
end

project_folder_id = project_folder_result.on_failure { |failed_result| return failed_result }.result

# TODO: Do Something when this fails
ProjectStorages::UpdateService.new(user:, model: target)
.call(project_folder_id:, project_folder_mode: source.project_folder_mode)
.call(project_folder_id: project_folder_result.result[:id],
project_folder_mode: source.project_folder_mode)

# We only get here on a successful execution
create_target_file_links(source, target, new_work_package_map, user)
# TODO: Collect errors
FileLinks::CopyFileLinksService.call(source:, target:, user:, work_packages_map: work_package_map)
end

private

def create_target_file_links(source, target, work_package_map, user)
source_file_links = FileLink
.includes(:creator)
.where(container_id: work_package_map.keys, container_type: "WorkPackage")

return create_unmanaged_file_links(source_file_links, work_package_map, user) if source.project_folder_manual?

target_files = Peripherals::Registry
.resolve("#{source.storage.short_provider_type}.queries.folder_files_file_ids_deep_query")
.call(storage: source.storage, folder: Peripherals::ParentFolder.new(target.project_folder_location))
.result

source_files = Peripherals::Registry
.resolve("#{source.storage.short_provider_type}.queries.files_info")
.call(storage: source.storage, user:, file_ids: source_file_links.pluck(:origin_id))
.result

source_location_map = source_files.to_h { |info| [info.id, info.location] }

source_file_links.find_each do |source_link|
attributes = source_link.dup.attributes

attributes['creator_id'] = user.id
attributes['container_id'] = work_package_map[source_link.container_id]

source_link_location = source_location_map[source_link.origin_id]
target_link_location = source_link_location.gsub(source.managed_project_folder_path, target.managed_project_folder_path)

attributes['origin_id'] = target_files[target_link_location]
def prepare_polling(result)
return if result[:id]

FileLinks::CreateService.new(user:).call(attributes)
end
end

def create_unmanaged_file_links(source_file_links, work_package_map, user)
source_file_links.find_each do |source_file_link|
attributes = source_file_link.dup.attributes

attributes['creator_id'] = user.id
attributes['container_id'] = work_package_map[source_file_link.container_id]

# TODO: Do something when this fails
FileLinks::CreateService.new(user:).call(attributes)
end
end

def initiate_project_folder_copy(source, target)
return ServiceResult.success if source.project_folder_inactive?
return ServiceResult.success(result: source.project_folder_id) if source.project_folder_manual?

copy_result = issue_command(source, target).on_failure { |failed_result| return failed_result }.result
return ServiceResult.success(result: copy_result[:id]) if copy_result[:id]

Thread.current[job_id] = copy_result[:url]
Thread.current[job_id] = result[:url]
raise Errors::PollingRequired, "#{job_id} Storage requires polling"
end

def issue_command(source, target)
Peripherals::Registry
.resolve("#{source.storage.short_provider_type}.commands.copy_template_folder")
.call(storage: source.storage,
source_path: source.project_folder_location,
destination_path: target.managed_project_folder_path)
end

def polling?
!!Thread.current[job_id]
end
Expand All @@ -110,7 +78,7 @@ def results_from_polling
raise(Errors::PollingRequired, "#{job_id} Polling not completed yet") if response[:status] != 'completed'

Thread.current[job_id] = nil
ServiceResult.success(result: response[:resourceId])
ServiceResult.success(result: { id: response[:resourceId] })
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
expect(command_result).to be_success
expect(command_result.result[:url]).to match %r</drives/#{storage.drive_id}/items/.+\?.+$>
ensure
delete_copied_folder(command_result.result[:id])
delete_copied_folder(command_result.result[:url])
end

describe 'error handling' do
Expand Down Expand Up @@ -190,7 +190,11 @@ def existing_folder_tuples
end
end

def delete_copied_folder(location)
def delete_copied_folder(url)
extractor_regex = /.+\/items\/(?<item_id>\w+)\?/
match_data = extractor_regex.match(url)
location = match_data[:item_id]

Storages::Peripherals::Registry
.resolve('one_drive.commands.delete_folder')
.call(storage:, location:)
Expand Down
Loading

0 comments on commit a714787

Please sign in to comment.