Skip to content

Commit

Permalink
Merge pull request #14204 from opf/implementation/50908-implement-red…
Browse files Browse the repository at this point in the history
…irect-to-project-foler-with-waiting-under-projectsproject_idproject_storagesidopen

[#50908] Implement redirect to project folder with waiting under /projects/:project_id/project_storages/:id/open
  • Loading branch information
ba1ash committed Nov 30, 2023
2 parents 3a6df28 + fca4365 commit 45deda6
Show file tree
Hide file tree
Showing 17 changed files with 635 additions and 60 deletions.
9 changes: 8 additions & 1 deletion app/helpers/application_helper.rb
Expand Up @@ -119,7 +119,14 @@ def due_date_distance_in_words(date)
def render_flash_messages
messages = flash
.reject { |k, _| k.start_with? '_' }
.map { |k, v| render_flash_message(k, v) }
.map do |k, v|
if k.to_sym == :modal
component = v[:type].constantize
component.new(**v[:parameters]).render_in(self)
else
render_flash_message(k, v)
end
end

safe_join messages, "\n"
end
Expand Down
@@ -0,0 +1,82 @@
/*
* -- copyright
* OpenProject is an open source project management software.
* Copyright (C) 2023 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.
* ++
*/

import { Controller } from '@hotwired/stimulus';
import { renderStreamMessage } from '@hotwired/turbo';
import { ModalDialogElement } from '@openproject/primer-view-components/app/components/primer/alpha/modal_dialog';

export default class OpenProjectStorageModalController extends Controller<ModalDialogElement> {
static values = {
projectStorageOpenUrl: String,
redirectUrl: String,
};

interval:number;
projectStorageOpenUrlValue:string;
redirectUrlValue:string;

connect() {
this.element.open = true;
this.interval = 0;
this.load();
this.element.addEventListener('close', () => { this.disconnect(); });
this.element.addEventListener('cancel', () => { this.disconnect(); });
}

disconnect() {
clearInterval(this.interval);
}

load() {
this.interval = setTimeout(
async () => {
const response = await fetch(
this.projectStorageOpenUrlValue,
{
headers: {
Accept: 'text/vnd.turbo-stream.html',
},
},
);
if (response.status === 200) {
const streamActionHTML = await response.text();
renderStreamMessage(streamActionHTML);
setTimeout(
() => { window.location.href = this.redirectUrlValue; },
2000,
);
} else {
this.load();
}
},
3000,
);
}
}
@@ -0,0 +1,11 @@
<%= render(
Primer::Alpha::Dialog.new(
id: self.class.dialog_id,
title: 'Modal',
visually_hide_title: true,
data: @data)
) do |dialog| %>
<% dialog.with_body(id: self.class.dialog_body_id) do %>
<%= render(self.class::Body.new(@state)) %>
<% end %>
<% end %>
@@ -0,0 +1,47 @@
# Copyright (C) 2012-2023 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.
#++

class Storages::OpenProjectStorageModalComponent < ViewComponent::Base
def initialize(project_storage_open_url:, redirect_url:, state:, **options)
super
controller = 'storages--open-project-storage-modal'
@data = {
controller:,
'application-target': 'dynamic',
"#{controller}-project-storage-open-url-value": project_storage_open_url,
"#{controller}-redirect-url-value": redirect_url
}
@state = state
end

def self.dialog_id
'open-project-storage-modal-component'
end

def self.dialog_body_id
'open-project-storage-modal-body-component'
end
end
@@ -0,0 +1,28 @@
<% case @state %>
<% when :waiting %>
<%= render(Primer::OpenProject::FlexLayout.new(align_items: :center)) do |flex| %>
<% flex.with_row do %>
<div class="op-loading-indicator">
<div></div><div></div>
</div>
<% end %>
<% flex.with_row do %>
<%= render(Primer::Beta::Text.new) { I18n.t('storages.open_project_storage_modal.waiting.a') } %>
<% end %>
<% flex.with_row do %>
<%= render(Primer::Beta::Text.new) { I18n.t('storages.open_project_storage_modal.waiting.b') } %>
<% end %>
<% end %>
<% when :success %>
<%= render(Primer::OpenProject::FlexLayout.new(align_items: :center)) do |flex| %>
<% flex.with_row do %>
<%= render(Primer::Beta::Octicon.new(:"check-circle", size: :medium, color: :success)) %>
<% end %>
<% flex.with_row do %>
<%= render(Primer::Beta::Text.new) { I18n.t('storages.open_project_storage_modal.success.a') } %>
<% end %>
<% flex.with_row do %>
<%= render(Primer::Beta::Text.new) { I18n.t('storages.open_project_storage_modal.success.b') } %>
<% end %>
<% end %>
<% end %>
@@ -0,0 +1,31 @@
# Copyright (C) 2012-2023 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.
#++

class Storages::OpenProjectStorageModalComponent::Body < ViewComponent::Base
def initialize(state)
@state = state
end
end
@@ -0,0 +1,114 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 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.
#++

class Storages::ProjectStoragesController < ApplicationController
using Storages::Peripherals::ServiceResultRefinements

menu_item :overview
model_object Storages::ProjectStorage

before_action :require_login
before_action :find_model_object
before_action :find_project_by_project_id
before_action :render_403, unless: -> { User.current.allowed_in_project?(:view_file_links, @project) }

def open
storage_open_url = @object.open(current_user).match(
on_success: ->(url) { url },
on_failure: ->(error) { raise_error(error) }
)
if @object.project_folder_automatic?
storage = @object.storage
# check if user "see" project_folder
::Storages::Peripherals::Registry
.resolve("queries.#{storage.short_provider_type}.file_info")
.call(storage:,
user: current_user,
file_id: @object.project_folder_id)
.match(
on_success: user_can_read_project_folder(storage_open_url:),
on_failure: user_can_not_read_project_folder(storage:, storage_open_url:)
)
else
redirect_to storage_open_url
end
end

private

def user_can_read_project_folder(storage_open_url:)
->(_) do
respond_to do |format|
format.turbo_stream do
render(
turbo_stream: OpTurbo::StreamComponent.new(
action: :update,
target: Storages::OpenProjectStorageModalComponent.dialog_body_id,
template: Storages::OpenProjectStorageModalComponent::Body.new(:success).render_in(view_context)
).render_in(view_context)
)
end
format.html { redirect_to storage_open_url }
end
end
end

def user_can_not_read_project_folder(storage_open_url:, storage:)
->(result) do
respond_to do |format|
format.turbo_stream { head :no_content }
format.html do
case result.code
when :unauthorized
redirect_to(
oauth_clients_ensure_connection_url(
oauth_client_id: storage.oauth_client.client_id,
storage_id: storage.id,
destination_url: request.url
)
)
when :forbidden
redirect_to(
project_overview_path(project_id: @project.identifier),
flash: {
modal: {
type: 'Storages::OpenProjectStorageModalComponent',
parameters: {
project_storage_open_url: request.path,
redirect_url: storage_open_url,
state: :waiting
}
}
}
)
end
end
end
end
end
end
14 changes: 14 additions & 0 deletions modules/storages/app/models/storages/project_storage.rb
Expand Up @@ -68,6 +68,20 @@ def file_inside_project_folder?(escaped_file_path)
escaped_file_path.match?(%r|^/#{project_folder_path_escaped}|)
end

def open(user)
if project_folder_inactive? ||
(project_folder_automatic? && !user.allowed_in_project?(:read_files, project)) ||
project_folder_id.blank?
Storages::Peripherals::Registry
.resolve("queries.#{storage.short_provider_type}.open_storage")
.call(storage:, user:)
else
Storages::Peripherals::Registry
.resolve("queries.#{storage.short_provider_type}.open_file_link")
.call(storage:, user:, file_id: project_folder_id)
end
end

private

def escape_path(path)
Expand Down
7 changes: 7 additions & 0 deletions modules/storages/config/locales/en.yml
Expand Up @@ -248,6 +248,13 @@ en:
automatically_managed_project_folder_missing: "To complete the setup, please configure automatically managed project folders for your storage."
notice_oauth_application_replaced: "The OpenProject OAuth application was successfully replaced."
notice_successful_storage_connection: "Storage connected successfully! Remember to activate the module and the specific storage in the project settings of each desired project to use it."
open_project_storage_modal:
waiting:
a: "We are setting up your permissions on the project folder."
b: "One moment please, this might take some time..."
success:
a: "Integration setup completed"
b: "You are being redirected"
health:
title: "Managed folders status"
label_pending: "Pending"
Expand Down
4 changes: 4 additions & 0 deletions modules/storages/config/routes.rb
Expand Up @@ -51,6 +51,10 @@
end
end

get 'projects/:project_id/project_storages/:id/open',
controller: 'storages/project_storages',
action: 'open',
as: 'open_project_storage'
scope 'projects/:project_id', as: 'project' do
namespace 'settings' do
resources :project_storages, controller: '/storages/admin/project_storages', except: %i[show] do
Expand Down

0 comments on commit 45deda6

Please sign in to comment.