diff --git a/app/components/admin/attachments_settings_header_component.html.erb b/app/components/admin/attachments_settings_header_component.html.erb
index d3ed6cdb3801..52ccc429b460 100644
--- a/app/components/admin/attachments_settings_header_component.html.erb
+++ b/app/components/admin/attachments_settings_header_component.html.erb
@@ -30,10 +30,10 @@ See COPYRIGHT and LICENSE files for more details.
<% helpers.html_title t(:label_administration), @title %>
<%= render(Primer::OpenProject::PageHeader.new(border_bottom: 0)) do |header| %>
- <% header.with_title { @title } %>
+ <% header.with_title { t(:"attributes.attachments") } %>
<% header.with_breadcrumbs([{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_storages_path, text: t("project_module_storages") },
- @title]) %>
+ t(:"attributes.attachments")]) %>
<% end %>
<%= render(Primer::Alpha::TabNav.new(label: "label")) do |component|
diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml
index e059c4d1c234..22784273566f 100644
--- a/config/locales/js-en.yml
+++ b/config/locales/js-en.yml
@@ -399,7 +399,6 @@ en:
# Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release.
"14_1":
standard:
- learn_about_link: https://www.openproject.org/docs/release-notes/14-1-0/
new_features_html: >
The release contains various new features and improvements:
diff --git a/docker/prod/web b/docker/prod/web
index 92d70da01707..f53e8bd937db 100755
--- a/docker/prod/web
+++ b/docker/prod/web
@@ -17,5 +17,8 @@ if [ "$MIGRATE" = "true" ]; then
bundle exec rake db:migrate
fi
+# Clean up any dangling PID file
+rm -f ${APP_PATH}/tmp/pids/*
+
# see `config/puma.rb` for configuration
exec bundle exec rails server -u puma -b $BIND -p $PORT
diff --git a/docs/system-admin-guide/integrations/nextcloud/NC_admin-defaults-user-options.png b/docs/system-admin-guide/integrations/nextcloud/NC_admin-defaults-user-options.png
index cd43a1f4a326..6c627c0e55ae 100644
Binary files a/docs/system-admin-guide/integrations/nextcloud/NC_admin-defaults-user-options.png and b/docs/system-admin-guide/integrations/nextcloud/NC_admin-defaults-user-options.png differ
diff --git a/docs/system-admin-guide/integrations/nextcloud/README.md b/docs/system-admin-guide/integrations/nextcloud/README.md
index 8a8354e94757..e238498bb820 100644
--- a/docs/system-admin-guide/integrations/nextcloud/README.md
+++ b/docs/system-admin-guide/integrations/nextcloud/README.md
@@ -69,7 +69,9 @@ In the configuration page that appears, you'll see a blank text field titled **O
Click on the **Save** button.
-> **Note:** If the OpenProject host cannot be added, you may check the [Troubleshooting](#troubleshooting) section at the bottom of this page
+> **Note:** If the OpenProject host cannot be added, you may check the [Troubleshooting](#troubleshooting) section at the bottom of this page.
+
+Please note, when you use the **Terms of Service** app on the Nextcloud side, all terms also need to be accepted for the OpenProject user that gets created during the setup. This is set to happen automatically during the initial set-up. If you see an error message indicating otherwise or the integration does not behave as expected, please refer to the [Troubleshooting](#troubleshooting) section at the bottom of this page.
The next part of the setup will require you to enter OpenProject OAuth values here, but before we do that, you will need to generate them in OpenProject. To do so, navigate to your OpenProject instance in a new browser tab.
@@ -216,6 +218,14 @@ Some administrators setup OpenProject using a self signed TLS/SSL certificate wi
Attention: Please do not confuse the CA for the Nextcloud server's certificate with the CA of the OpenProject server's certificate which you might have provided in the OpenProject installation wizard. They do not necessarily need to be the same.
+#### Error message "Sign terms of services"
+
+**Terms of services** is an app on the Nextcloud side of integration that makes it mandatory for users to accept terms of services before Nextcloud can be used. In order for the integration to work properly the OpenProject user also needs to accept all terms that are set up. It should be accepted automatically during the set up process. However, it is possible that in certain situations it does not happen automatically.
+
+
+To fix this please log into Nextcloud, proceed to Administration and select OpenProject. This will trigger an automatic background check and suggest that *Terms of services* be signed.
+![Fix a Terms of services error in Nextcloud](openproject_system_guide_tos_fix.png)
+
#### While setting up Project folders
While setting up the project folders we create a new user, group and group folder named `OpenProject`. At the time of set up the system expects either all of these entities to have been set up with proper permissions or none of them to be present. If one or more of these entities are present without required permissions, an error message will be displayed.
diff --git a/docs/system-admin-guide/integrations/nextcloud/openproject_system_guide_tos_error.png b/docs/system-admin-guide/integrations/nextcloud/openproject_system_guide_tos_error.png
new file mode 100644
index 000000000000..87f89abe00e6
Binary files /dev/null and b/docs/system-admin-guide/integrations/nextcloud/openproject_system_guide_tos_error.png differ
diff --git a/docs/system-admin-guide/integrations/nextcloud/openproject_system_guide_tos_fix.png b/docs/system-admin-guide/integrations/nextcloud/openproject_system_guide_tos_fix.png
new file mode 100644
index 000000000000..ad0659bf8001
Binary files /dev/null and b/docs/system-admin-guide/integrations/nextcloud/openproject_system_guide_tos_fix.png differ
diff --git a/docs/user-guide/file-management/nextcloud-integration/NC_0_01-FileRelationSearch_new.png b/docs/user-guide/file-management/nextcloud-integration/NC_0_01-FileRelationSearch_new.png
new file mode 100644
index 000000000000..1cac28b1a64a
Binary files /dev/null and b/docs/user-guide/file-management/nextcloud-integration/NC_0_01-FileRelationSearch_new.png differ
diff --git a/docs/user-guide/file-management/nextcloud-integration/NC_1_00-FileWPRelation.png b/docs/user-guide/file-management/nextcloud-integration/NC_1_00-FileWPRelation.png
index 91f48cef4b91..bf08370bb15e 100644
Binary files a/docs/user-guide/file-management/nextcloud-integration/NC_1_00-FileWPRelation.png and b/docs/user-guide/file-management/nextcloud-integration/NC_1_00-FileWPRelation.png differ
diff --git a/docs/user-guide/file-management/nextcloud-integration/NC_1_00-FileWPRelation_new.png b/docs/user-guide/file-management/nextcloud-integration/NC_1_00-FileWPRelation_new.png
new file mode 100644
index 000000000000..c2453a473791
Binary files /dev/null and b/docs/user-guide/file-management/nextcloud-integration/NC_1_00-FileWPRelation_new.png differ
diff --git a/docs/user-guide/file-management/nextcloud-integration/NC_1_01-FileWPActions.png b/docs/user-guide/file-management/nextcloud-integration/NC_1_01-FileWPActions.png
index e615ceb65d3a..24aee5b97924 100644
Binary files a/docs/user-guide/file-management/nextcloud-integration/NC_1_01-FileWPActions.png and b/docs/user-guide/file-management/nextcloud-integration/NC_1_01-FileWPActions.png differ
diff --git a/docs/user-guide/file-management/nextcloud-integration/README.md b/docs/user-guide/file-management/nextcloud-integration/README.md
index ffef5758572d..4bd2bcaacb24 100644
--- a/docs/user-guide/file-management/nextcloud-integration/README.md
+++ b/docs/user-guide/file-management/nextcloud-integration/README.md
@@ -149,11 +149,11 @@ In the **Details** side panel, click on the **OpenProject** tab. This tab lets y
To link this file to a work package in OpenProject for the first time, use the search bar to find the correct work package (you can search either using a word in the title of the work package, or simply enter the work package ID) and click on it.
-![Search work package in Nextcloud](NC_0_01-FileRelationSearch.png)
+![Search work package in Nextcloud](NC_0_01-FileRelationSearch_new.png)
This linked file will then appear underneath the search bar. Doing so will also automatically add the file to the Files tab of the corresponding work package(s) in OpenProject.
-![Show linked work packages in Nextcloud](NC_1_00-FileWPRelation.png)
+![Show linked work packages in Nextcloud](NC_1_00-FileWPRelation_new.png)
#### Link multiple files to a work packages
diff --git a/frontend/src/app/core/routing/openproject.routes.ts b/frontend/src/app/core/routing/openproject.routes.ts
index b72d4b749bbf..c52b5783ce45 100644
--- a/frontend/src/app/core/routing/openproject.routes.ts
+++ b/frontend/src/app/core/routing/openproject.routes.ts
@@ -242,6 +242,29 @@ export function initializeUiRouterListeners(injector:Injector) {
(transition) => redirectToMobileAlternative(transition),
);
+ // Fire an event when navigating to a different module. This event then can be detected in
+ // the non-angular parts of the application. A usecase for this can be found in the
+ // overview-header.controllers.ts
+ // See https://community.openproject.org/wp/55024 for details.
+ $transitions.onBefore(
+ {},
+ (transition:Transition) => {
+ const fromState = transition.from();
+ const toState = transition.to();
+ if (
+ !!fromState.name
+ && !!toState.name
+ && fromState.name?.split('.')[0] !== toState.name?.split('.')[0]
+ ) {
+ window.dispatchEvent(new CustomEvent('angular:router:module-changed', {
+ detail: toState.name?.split('.')[0],
+ }));
+ }
+
+ return true;
+ },
+ );
+
// Apply classes from bodyClasses in each state definition
// This was defined as onEnter, onExit functions in each state before
// but since AOT doesn't allow anonymous functions, we can't re-use them now.
diff --git a/frontend/src/app/features/homescreen/blocks/new-features.component.ts b/frontend/src/app/features/homescreen/blocks/new-features.component.ts
index a167fa4c7b9b..aa0f262ff751 100644
--- a/frontend/src/app/features/homescreen/blocks/new-features.component.ts
+++ b/frontend/src/app/features/homescreen/blocks/new-features.component.ts
@@ -37,6 +37,8 @@ export const homescreenNewFeaturesBlockSelector = 'homescreen-new-features-block
// The key used in the I18n files to distinguish between versions.
const OpVersionI18n = '14_1';
+const OpReleaseURL = 'https://www.openproject.org/docs/release-notes/14-1-0/';
+
/** Update the teaser image to the next version */
const featureTeaserImage = `${OpVersionI18n}_features.svg`;
@@ -90,8 +92,7 @@ export class HomescreenNewFeaturesBlockComponent {
}
public get teaserWebsiteUrl() {
- const url = this.translated('learn_about_link');
- return this.domSanitizer.bypassSecurityTrustResourceUrl(url);
+ return this.domSanitizer.bypassSecurityTrustResourceUrl(OpReleaseURL);
}
public get currentNewFeatureHtml():string {
diff --git a/frontend/src/stimulus/controllers/dynamic/overview-header.controller.ts b/frontend/src/stimulus/controllers/dynamic/overview-header.controller.ts
new file mode 100644
index 000000000000..bfe845600c12
--- /dev/null
+++ b/frontend/src/stimulus/controllers/dynamic/overview-header.controller.ts
@@ -0,0 +1,52 @@
+/*
+ * -- 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';
+
+export default class OverviewHeaderController extends Controller {
+ connect() {
+ window.addEventListener('angular:router:module-changed', this.toggleHeaderVisibility);
+ }
+
+ disconnect() {
+ window.removeEventListener('angular:router:module-changed', this.toggleHeaderVisibility);
+ }
+
+ toggleHeaderVisibility = (event:CustomEvent) => {
+ const name = event.detail as string;
+ const element = this.element as HTMLElement;
+
+ if (name === 'overview') {
+ element.classList.remove('d-none');
+ } else {
+ element.classList.add('d-none');
+ }
+ };
+}
diff --git a/lib/redmine/menu_manager/menu_helper.rb b/lib/redmine/menu_manager/menu_helper.rb
index 5a94f02ad66b..8ad50794d7a3 100644
--- a/lib/redmine/menu_manager/menu_helper.rb
+++ b/lib/redmine/menu_manager/menu_helper.rb
@@ -152,7 +152,7 @@ def render_wrapped_menu_parent_node(node, project)
html_id = node.html_options[:id] || node.name
content_tag(:div, class: "main-item-wrapper", id: "#{html_id}-wrapper") do
concat render_single_menu_node(node, project)
- concat render_menu_toggler
+ concat render_menu_toggler(node.name)
end
end
@@ -163,11 +163,14 @@ def render_wrapped_single_node(node, project)
end
end
- def render_menu_toggler
+ def render_menu_toggler(node_name)
content_tag(:button,
class: "toggler main-menu-toggler",
type: :button,
- data: { action: "menus--main#descend" }) do
+ data: {
+ action: "menus--main#descend",
+ test_selector: "main-menu-toggler--#{node_name}"
+ }) do
render(Primer::Beta::Octicon.new("arrow-right", size: :small))
end
end
diff --git a/modules/boards/spec/features/board_navigation_spec.rb b/modules/boards/spec/features/board_navigation_spec.rb
index b251b0bab8b4..9fa5a359a0f4 100644
--- a/modules/boards/spec/features/board_navigation_spec.rb
+++ b/modules/boards/spec/features/board_navigation_spec.rb
@@ -104,8 +104,7 @@
with_settings: { notifications_polling_interval: 1_000 } do
visit project_path(project)
- item = page.find('#menu-sidebar li[data-name="boards"]', wait: 10)
- item.find(".toggler").click
+ page.find_test_selector("main-menu-toggler--boards", wait: 10).click
subitem = page.find_test_selector("op-sidemenu--item-action--Myboard", wait: 10)
# Ends with boards due to lazy route
diff --git a/modules/gantt/spec/features/timeline/timeline_navigation_spec.rb b/modules/gantt/spec/features/timeline/timeline_navigation_spec.rb
index 6664d1f43dcb..598d39f94744 100644
--- a/modules/gantt/spec/features/timeline/timeline_navigation_spec.rb
+++ b/modules/gantt/spec/features/timeline/timeline_navigation_spec.rb
@@ -118,7 +118,7 @@
# Navigate to the WP module
find(".main-menu--arrow-left-to-project").click
- find("#main-menu-work-packages-wrapper .main-menu-toggler").click
+ page.find_test_selector("main-menu-toggler--work_packages").click
# Select other query
query_menu.select query
@@ -128,7 +128,7 @@
# Navigate to the Gantt module agin
find(".main-menu--arrow-left-to-project").click
- find("#main-menu-gantt-wrapper .main-menu-toggler").click
+ page.find_test_selector("main-menu-toggler--gantt").click
# Select first query again
query_menu.click_item query_tl.name
diff --git a/modules/overviews/app/views/overviews/overviews/show.html.erb b/modules/overviews/app/views/overviews/overviews/show.html.erb
index f79c9c3de55f..bd1d6114ccb7 100644
--- a/modules/overviews/app/views/overviews/overviews/show.html.erb
+++ b/modules/overviews/app/views/overviews/overviews/show.html.erb
@@ -3,7 +3,13 @@
<% end -%>
<%=
- render(Primer::OpenProject::PageHeader.new(data: { turbo: true })) do |header|
+ render(Primer::OpenProject::PageHeader.new(
+ data: {
+ 'controller': 'overview-header',
+ 'application-target': 'dynamic',
+ turbo: true
+ }
+ )) do |header|
header.with_title(variant: :medium) { t("overviews.label") }
header.with_breadcrumbs(
[
diff --git a/modules/overviews/spec/features/navigation_spec.rb b/modules/overviews/spec/features/navigation_spec.rb
index ca23d402c4ac..e38a43185da0 100644
--- a/modules/overviews/spec/features/navigation_spec.rb
+++ b/modules/overviews/spec/features/navigation_spec.rb
@@ -52,4 +52,53 @@
.to have_content("Overview")
end
end
+
+ context "as user with permissions" do
+ let(:project) { create(:project, enabled_module_names: %i[work_package_tracking]) }
+ let(:user) { create(:admin) }
+ let(:query) do
+ create(:query_with_view_work_packages_table,
+ project:,
+ user:,
+ name: "My important Query")
+ end
+
+ before do
+ query
+ login_as user
+ end
+
+ it "can navigate to other modules (regression #55024)" do
+ visit project_overview_path(project.id)
+
+ # Expect page to be loaded
+ within "#content" do
+ expect(page).to have_content("Overview")
+ end
+
+ # Navigate to the WP module
+ page.find_test_selector("main-menu-toggler--work_packages").click
+
+ # Click on a saved query
+ page.find_test_selector("op-sidemenu--item-action--MyimportantQuery", wait: 10).click
+
+ loading_indicator_saveguard
+
+ within "#content" do
+ # Expect the query content to be shown
+ expect(page).to have_field("editable-toolbar-title", with: query.name)
+
+ # Expect no page header of the Overview to be shown any more
+ expect(page).to have_no_content("Overview")
+ end
+
+ # Navigate back to the Overview page
+ page.execute_script("window.history.back()")
+
+ # Expect page to be loaded
+ within "#content" do
+ expect(page).to have_content("Overview")
+ end
+ end
+ end
end
diff --git a/modules/storages/app/controllers/storages/admin/project_storages_controller.rb b/modules/storages/app/controllers/storages/admin/project_storages_controller.rb
index 4ad274a0412a..17e9edaee424 100644
--- a/modules/storages/app/controllers/storages/admin/project_storages_controller.rb
+++ b/modules/storages/app/controllers/storages/admin/project_storages_controller.rb
@@ -26,40 +26,21 @@
# See COPYRIGHT and LICENSE files for more details.
#++
-# This controller manages the creation and deletion of ProjectStorage objects.
-# ProjectStorages belong to projects and indicate that the respective
-# Storage (i.e. a Nextcloud server) is enabled in the project.
-# Please see the standard Rails documentation on controllers:
-# https://guides.rubyonrails.org/action_controller_overview.html
-# Called by: Calls to the controller methods are initiated by user Web GUI
-# actions and mapped to this controller by storages/config/routes.rb.
class Storages::Admin::ProjectStoragesController < Projects::SettingsController
- # This is the resource handled in this controller.
- # So the controller knows that the ID in params (URl) refer to instances of this model.
- # This defines @object as the model instance.
model_object Storages::ProjectStorage
before_action :find_model_object, only: %i[oauth_access_grant edit update destroy destroy_info]
- # No need to before_action :find_project_by_project_id as SettingsController already checks
- # No need to check for before_action :authorize, as the SettingsController already checks this.
-
- # This MenuController method defines the default menu item to be used (highlighted)
- # when rendering the main menu in the left (part of the base layout).
- # The menu item itself is registered in modules/storages/lib/open_project/storages/engine.rb
menu_item :settings_project_storages
- # Show a HTML page with the list of ProjectStorages
- # Called by: Project -> Settings -> File Storages
- def index
- # Just get the list of ProjectStorages associated with the project
+ def external_file_storages
@project_storages = Storages::ProjectStorage.where(project: @project).includes(:storage)
- # Render the list storages using ViewComponents in the /app/components folder which defines
- # the ways rows are rendered in a table layout.
- render "/storages/project_settings/index"
+ render "/storages/project_settings/external_file_storages"
+ end
+
+ def attachments
+ render "/storages/project_settings/attachments"
end
- # Show a HTML page with a form in order to create a new ProjectStorage
- # Called by: When a user clicks on the "+New" button in Project -> Settings -> File Storages
def new
@available_storages = available_storages
project_folder_mode = project_folder_mode_from_params
@@ -74,8 +55,6 @@ def new
render template: "/storages/project_settings/new"
end
- # Create a new ProjectStorage object.
- # Called by: The new page above with form-data from that form.
def create
service_result = ::Storages::ProjectStorages::CreateService
.new(user: current_user)
@@ -98,11 +77,11 @@ def oauth_access_grant # rubocop:disable Metrics/AbcSize
.authorization_state(storage:, user: current_user)
if auth_state == :connected
- redirect_to(project_settings_project_storages_path)
+ redirect_to(external_file_storages_project_settings_project_storages_path)
else
nonce = SecureRandom.uuid
cookies["oauth_state_#{nonce}"] = {
- value: { href: project_settings_project_storages_url(project_id: @project_storage.project_id),
+ value: { href: external_file_storages_project_settings_project_storages_url(project_id: @project_storage.project_id),
storageId: @project_storage.storage_id }.to_json,
expires: 1.hour
}
@@ -111,13 +90,7 @@ def oauth_access_grant # rubocop:disable Metrics/AbcSize
end
end
- # Edit page is very similar to new page, except that we don't need to set
- # default attribute values because the object already exists
- # Called by: Global app/config/routes.rb to serve Web page
def edit
- # Render existing ProjectStorage object
- # @object was calculated in before_action :find_model_object (see comments above).
- # @project_storage is used in the view in order to render the form for a new object
@project_storage = @object
@project_storage.project_folder_mode = project_folder_mode_from_params if project_folder_mode_from_params.present?
@@ -129,10 +102,6 @@ def edit
render "/storages/project_settings/edit"
end
- # Update is similar to create above
- # See also: create above
- # See also: https://www.openproject.org/docs/development/concepts/contracted-services/
- # Called by: Global app/config/routes.rb to serve Web page
def update
service_result = ::Storages::ProjectStorages::UpdateService
.new(user: current_user, model: @object)
@@ -148,19 +117,13 @@ def update
end
end
- # Purpose: Destroy a ProjectStorage object
- # Called by: By pressing a "Delete" icon in the Project's settings ProjectStorages page
- # It redirects back to the list of ProjectStorages in the project
def destroy
- # The complex logic for deleting associated objects was moved into a service:
- # https://dev.to/joker666/ruby-on-rails-pattern-service-objects-b19
Storages::ProjectStorages::DeleteService
.new(user: current_user, model: @object)
.call
.on_failure { |service_result| flash[:error] = service_result.errors.full_messages }
- # Redirect the user to the URL of Projects -> Settings -> File Storages
- redirect_to project_settings_project_storages_path
+ redirect_to external_file_storages_project_settings_project_storages_path
end
def destroy_info
@@ -171,10 +134,7 @@ def destroy_info
private
- # Define the list of permitted parameters for creating/updating a ProjectStorage.
- # Called by create and update actions above.
def permitted_storage_settings_params
- # "params" is an instance of ActionController::Parameters
params
.require(:storages_project_storage)
.permit("storage_id", "project_folder_mode", "project_folder_id")
@@ -197,7 +157,7 @@ def available_storages
def redirect_to_project_storages_path_with_oauth_access_grant_confirmation
if storage_oauth_access_granted?
- redirect_to project_settings_project_storages_path
+ redirect_to external_file_storages_project_settings_project_storages_path
else
redirect_to_project_storages_path_with_nudge_modal
end
@@ -210,7 +170,7 @@ def storage_oauth_access_granted?
def redirect_to_project_storages_path_with_nudge_modal
redirect_to(
- project_settings_project_storages_path,
+ external_file_storages_project_settings_project_storages_path,
flash: { modal: oauth_access_grant_nudge_modal }
)
end
diff --git a/modules/storages/app/views/storages/project_settings/_project_folder_form.html.erb b/modules/storages/app/views/storages/project_settings/_project_folder_form.html.erb
index 871598437d5d..c136a0db048b 100644
--- a/modules/storages/app/views/storages/project_settings/_project_folder_form.html.erb
+++ b/modules/storages/app/views/storages/project_settings/_project_folder_form.html.erb
@@ -158,7 +158,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= spot_icon('checkmark') %>
<%= content_tag :span, submit_button_label %>
<% end %>
- <%= link_to project_settings_project_storages_path(@project), class: 'button' do %>
+ <%= link_to external_file_storages_project_settings_project_storages_path(@project), class: 'button' do %>
<%= spot_icon('cancel') %>
<%= content_tag :span, t(:button_cancel) %>
<% end %>
diff --git a/modules/storages/app/views/storages/project_settings/_storage_select_form.html.erb b/modules/storages/app/views/storages/project_settings/_storage_select_form.html.erb
index a723e22d40d1..853b886e7776 100644
--- a/modules/storages/app/views/storages/project_settings/_storage_select_form.html.erb
+++ b/modules/storages/app/views/storages/project_settings/_storage_select_form.html.erb
@@ -48,7 +48,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= spot_icon('checkmark') %>
<%= content_tag :span, submit_button_label %>
<% end %>
- <%= link_to project_settings_project_storages_path(@project), class: 'button' do %>
+ <%= link_to external_file_storages_project_settings_project_storages_path(@project), class: 'button' do %>
<%= spot_icon('cancel') %>
<%= content_tag :span, t(:button_cancel) %>
<% end %>
diff --git a/modules/storages/app/views/storages/project_settings/index.html.erb b/modules/storages/app/views/storages/project_settings/attachments.html.erb
similarity index 61%
rename from modules/storages/app/views/storages/project_settings/index.html.erb
rename to modules/storages/app/views/storages/project_settings/attachments.html.erb
index 81c39f7d5e04..27290a91a05c 100644
--- a/modules/storages/app/views/storages/project_settings/index.html.erb
+++ b/modules/storages/app/views/storages/project_settings/attachments.html.erb
@@ -47,42 +47,39 @@ See COPYRIGHT and LICENSE files for more details.
end
%>
-<%= render(Primer::Alpha::TabPanels.new(label: "label", align: :left)) do |component|
- component.with_tab(selected: true, id: "tab-1") do |tab|
+<%= render(Primer::Alpha::TabNav.new(label: nil)) do |component|
+ component.with_tab(selected: false, href: external_file_storages_project_settings_project_storages_path) do |tab|
tab.with_text { t(:"external_file_storages") }
- tab.with_panel {
- render(::Storages::ProjectStorages::TableComponent.new(rows: @project_storages))
- }
end
- component.with_tab(selected: false, id: "tab-2") do |tab|
+ component.with_tab(selected: true, href: attachments_project_settings_project_storages_path) do |tab|
tab.with_text { t(:"attributes.attachments") }
- tab.with_panel {
- render(Primer::OpenProject::FlexLayout.new(border: true , border_radius: 2, p: 3)) do |l|
- l.with_row(flex_layout: true, align_items: :center, justify_content: :space_between) do |row|
- row.with_column do
- render(Primer::Beta::Text.new(font_weight: :bold)) do
- t("storages.show_attachments_toggle.label")
- end
- end
- row.with_column do
- checked = if @project.deactivate_work_package_attachments.nil?
- Setting.show_work_package_attachments
- else
- !@project.deactivate_work_package_attachments
- end
- render(Primer::Alpha::ToggleSwitch.new(src: deactivate_work_package_attachments_project_path(@project),
- csrf_token: form_authenticity_token,
- status_label_position: :start,
- checked:))
+ end
+end
+%>
+
+<%= render(Primer::OpenProject::FlexLayout.new(p: 3)) do |l|
+ l.with_row(flex_layout: true, align_items: :center, justify_content: :space_between) do |row|
+ row.with_column do
+ render(Primer::Beta::Text.new(font_weight: :bold)) do
+ t("storages.show_attachments_toggle.label")
end
end
- l.with_row do
- render(Primer::Beta::Text.new(color: :muted)) do
- t("storages.show_attachments_toggle.description")
+ row.with_column do
+ checked = if @project.deactivate_work_package_attachments.nil?
+ Setting.show_work_package_attachments
+ else
+ !@project.deactivate_work_package_attachments
end
+ render(Primer::Alpha::ToggleSwitch.new(src: deactivate_work_package_attachments_project_path(@project),
+ csrf_token: form_authenticity_token,
+ status_label_position: :start,
+ checked:))
end
end
- }
- end
-end
+ l.with_row do
+ render(Primer::Beta::Text.new(color: :muted)) do
+ t("storages.show_attachments_toggle.description")
+ end
+ end
+ end
%>
diff --git a/modules/storages/app/views/storages/project_settings/destroy_info.html.erb b/modules/storages/app/views/storages/project_settings/destroy_info.html.erb
index 9da3cc13f4b6..2a8a8d866991 100644
--- a/modules/storages/app/views/storages/project_settings/destroy_info.html.erb
+++ b/modules/storages/app/views/storages/project_settings/destroy_info.html.erb
@@ -53,7 +53,7 @@ See COPYRIGHT and LICENSE files for more details.
concat content_tag :i, '', class: 'button--icon icon-delete'
concat content_tag :span, t(:button_delete), class: 'button--text'
end %>
- <%= link_to project_settings_project_storages_path(@project_storage_to_destroy.project),
+ <%= link_to external_file_storages_project_settings_project_storages_path(@project_storage_to_destroy.project),
title: t(:button_cancel),
class: 'button -with-icon icon-cancel' do %>
<%= t(:button_cancel) %>
diff --git a/modules/storages/app/views/storages/project_settings/external_file_storages.html.erb b/modules/storages/app/views/storages/project_settings/external_file_storages.html.erb
new file mode 100644
index 000000000000..b98050d4f73e
--- /dev/null
+++ b/modules/storages/app/views/storages/project_settings/external_file_storages.html.erb
@@ -0,0 +1,60 @@
+<%#-- 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.
+
+++#%>
+
+<% html_title t(:label_administration), t("project_module_storages") %>
+
+<%= render(Primer::OpenProject::PageHeader.new(border_bottom: 0)) do |header|
+ header.with_title { t("project_module_storages") }
+ header.with_breadcrumbs([{ href: project_settings_general_path, text: t("label_project_settings") },
+ t("project_module_storages")])
+ header.with_action_button(tag: :a,
+ href: new_project_settings_project_storage_path,
+ scheme: :primary,
+ mobile_icon: :plus,
+ mobile_label: t(:'storages.label_storage'),
+ size: :medium,
+ aria: { label: t(:'storages.label_new_storage') },
+ title: t(:'storages.label_new_storage')) do |button|
+ button.with_leading_visual_icon(icon: :plus)
+ t(:'storages.label_storage')
+ end
+end
+%>
+
+<%= render(Primer::Alpha::TabNav.new(label: nil)) do |component|
+ component.with_tab(selected: true, href: external_file_storages_project_settings_project_storages_path) do |tab|
+ tab.with_text { t(:"external_file_storages") }
+ end
+ component.with_tab(selected: false, href: attachments_project_settings_project_storages_path) do |tab|
+ tab.with_text { t(:"attributes.attachments") }
+ end
+end
+%>
+
+<%= render(::Storages::ProjectStorages::TableComponent.new(rows: @project_storages)) %>
diff --git a/modules/storages/app/views/storages/project_settings/project_storage_members/index.html.erb b/modules/storages/app/views/storages/project_settings/project_storage_members/index.html.erb
index 84b7686031f6..84ff6c317a05 100644
--- a/modules/storages/app/views/storages/project_settings/project_storage_members/index.html.erb
+++ b/modules/storages/app/views/storages/project_settings/project_storage_members/index.html.erb
@@ -31,7 +31,7 @@ See COPYRIGHT and LICENSE files for more details.
<%
breadcrumb_paths(
ActionController::Base.helpers
- .link_to(t("project_module_storages"), project_settings_project_storages_path(@project)),
+ .link_to(t("project_module_storages"), external_file_storages_project_settings_project_storages_path(@project)),
default_breadcrumb
)
%>
diff --git a/modules/storages/config/locales/en.yml b/modules/storages/config/locales/en.yml
index 74e6e7acefec..55633291aed1 100644
--- a/modules/storages/config/locales/en.yml
+++ b/modules/storages/config/locales/en.yml
@@ -41,15 +41,18 @@ en:
errors:
too_many_elements_created_at_once: Too many elements created at once. Expected %{max} at most, got %{actual}.
external_file_storages: External file storages
- permission_create_files: 'External Storage: Create files'
- permission_delete_files: 'External Storage: Delete files'
+ permission_create_files: 'Automatically managed project folders: Create files'
+ permission_create_files_explanation: This permission is only available for Nextcloud storages
+ permission_delete_files: 'Automatically managed project folders: Delete files'
+ permission_delete_files_explanation: This permission is only available for Nextcloud storages
permission_header_for_project_module_storages: Automatically managed project folders
permission_manage_file_links: Manage file links
permission_manage_storages_in_project: Manage file storages in project
- permission_read_files: 'External Storage: Read files'
- permission_share_files: 'External Storage: Share files'
+ permission_read_files: 'Automatically managed project folders: Read files'
+ permission_share_files: 'Automatically managed project folders: Share files'
+ permission_share_files_explanation: This permission is only available for Nextcloud storages
permission_view_file_links: View file links
- permission_write_files: 'External Storage: Write files'
+ permission_write_files: 'Automatically managed project folders: Write files'
project_module_storages: Files
storages:
buttons:
diff --git a/modules/storages/config/routes.rb b/modules/storages/config/routes.rb
index 1d4786c30fe2..30617b2ca52b 100644
--- a/modules/storages/config/routes.rb
+++ b/modules/storages/config/routes.rb
@@ -65,7 +65,11 @@
scope "projects/:project_id", as: "project" do
namespace "settings" do
- resources :project_storages, controller: "/storages/admin/project_storages", except: %i[show] do
+ resources :project_storages, controller: "/storages/admin/project_storages", except: %i[index show] do
+ collection do
+ get :external_file_storages
+ get :attachments
+ end
member do
get :oauth_access_grant
# Destroy uses a get request to prompt the user before the actual DELETE request
diff --git a/modules/storages/lib/open_project/storages/engine.rb b/modules/storages/lib/open_project/storages/engine.rb
index 8a2b18f379ba..49e1121842c9 100644
--- a/modules/storages/lib/open_project/storages/engine.rb
+++ b/modules/storages/lib/open_project/storages/engine.rb
@@ -129,12 +129,24 @@ def self.permissions
# Independent of storages module (Disabling storages module does not revoke enabled permissions).
project_module nil, order: 100 do
permission :manage_storages_in_project,
- { "storages/admin/project_storages": %i[index members new
- edit update create oauth_access_grant
- destroy destroy_info set_permissions],
+ { "storages/admin/project_storages": %i[external_file_storages
+ attachments
+ members
+ index
+ new
+ edit
+ update
+ create
+ oauth_access_grant
+ destroy
+ destroy_info
+ set_permissions],
"storages/project_settings/project_storage_members": %i[index] },
permissible_on: :project,
dependencies: %i[]
+ OpenProject::Storages::Engine.permissions.each do |p|
+ permission(p, {}, permissible_on: :project, dependencies: %i[])
+ end
end
# Dependent on work_package_tracking module
@@ -151,17 +163,6 @@ def self.permissions
contract_actions: { file_links: %i[manage] }
end
- # Dependent on storages module (Disabling storages module does revoke enabled permissions).
- project_module :storages,
- dependencies: :work_package_tracking do
- OpenProject::Storages::Engine.permissions.each do |p|
- permission(p, {}, permissible_on: :project, dependencies: %i[])
- end
- end
-
- # Menu extensions
- # Add a "storages_admin_settings" to the admin_menu with the specified link,
- # condition ("if:"), caption and icon.
menu :admin_menu,
:files,
{ controller: "/storages/admin/storages", action: :index },
@@ -185,7 +186,7 @@ def self.permissions
menu :project_menu,
:settings_project_storages,
- { controller: "/storages/admin/project_storages", action: "index" },
+ { controller: "/storages/admin/project_storages", action: "external_file_storages" },
if: lambda { |project| User.current.allowed_in_project?(:manage_storages_in_project, project) },
caption: :project_module_storages,
parent: :settings
diff --git a/modules/storages/spec/features/delete_project_storage_and_file_links_spec.rb b/modules/storages/spec/features/delete_project_storage_and_file_links_spec.rb
index 182ff0f0e135..10536d280c61 100644
--- a/modules/storages/spec/features/delete_project_storage_and_file_links_spec.rb
+++ b/modules/storages/spec/features/delete_project_storage_and_file_links_spec.rb
@@ -66,7 +66,7 @@
it "deletes ProjectStorage with dependent FileLinks" do
# Go to Projects -> Settings -> File Storages
- visit project_settings_project_storages_path(project)
+ visit external_file_storages_project_settings_project_storages_path(project)
# The list of enabled file storages should now contain Storage 1
expect(page).to have_selector('h1', text: 'Files')
@@ -82,7 +82,7 @@
# Cancel Confirmation
page.click_link("Cancel")
- expect(page).to have_current_path project_settings_project_storages_path(project)
+ expect(page).to have_current_path external_file_storages_project_settings_project_storages_path(project)
page.find(".icon.icon-delete").click
@@ -91,7 +91,7 @@
page.click_button("Delete")
# List of ProjectStorages empty again
- expect(page).to have_current_path project_settings_project_storages_path(project)
+ expect(page).to have_current_path external_file_storages_project_settings_project_storages_path(project)
expect(page).to have_text(I18n.t("storages.no_results"))
# Also check in the database that ProjectStorage and dependent FileLinks are gone
diff --git a/modules/storages/spec/features/hide_attachments_spec.rb b/modules/storages/spec/features/hide_attachments_spec.rb
index 88019f9cd88b..44a219d2a444 100644
--- a/modules/storages/spec/features/hide_attachments_spec.rb
+++ b/modules/storages/spec/features/hide_attachments_spec.rb
@@ -54,7 +54,7 @@
it "changes database value" do
login_as current_user
- visit project_settings_project_storages_path(project)
+ visit external_file_storages_project_settings_project_storages_path(project)
click_on("Attachments")
expect(page).to have_css("toggle-switch", text: "Off")
@@ -79,7 +79,7 @@
login_as current_user
- visit project_settings_project_storages_path(project)
+ visit external_file_storages_project_settings_project_storages_path(project)
click_on("Attachments")
expect(page).to have_css("toggle-switch", text: "Off")
@@ -94,7 +94,7 @@
login_as current_user
- visit project_settings_project_storages_path(project)
+ visit external_file_storages_project_settings_project_storages_path(project)
click_on("Attachments")
expect(page).to have_css("toggle-switch", text: "On")
diff --git a/modules/storages/spec/features/manage_project_storage_spec.rb b/modules/storages/spec/features/manage_project_storage_spec.rb
index 27ee1733f37a..05fe84704969 100644
--- a/modules/storages/spec/features/manage_project_storage_spec.rb
+++ b/modules/storages/spec/features/manage_project_storage_spec.rb
@@ -110,7 +110,7 @@
# Check for an empty table in Project -> Settings -> File storages
expect(page).to have_title("Files")
- expect(page).to have_current_path project_settings_project_storages_path(project)
+ expect(page).to have_current_path external_file_storages_project_settings_project_storages_path(project)
expect(page).to have_text(I18n.t("storages.no_results"))
page.first(:link, 'New storage').click
@@ -118,7 +118,7 @@
expect(page).to have_current_path new_project_settings_project_storage_path(project_id: project)
expect(page).to have_text("Add a file storage")
page.click_link("Cancel")
- expect(page).to have_current_path project_settings_project_storages_path(project)
+ expect(page).to have_current_path external_file_storages_project_settings_project_storages_path(project)
# Enable one file storage together with a project folder mode
page.first(:link, 'New storage').click
@@ -179,7 +179,7 @@
storages_project_storage: {project_folder_mode: "inactive"})
expect(page).to have_text("Edit the file storage to this project")
page.click_link("Cancel")
- expect(page).to have_current_path project_settings_project_storages_path(project)
+ expect(page).to have_current_path external_file_storages_project_settings_project_storages_path(project)
# Press Delete icon to remove the storage from the project
page.find(".icon.icon-delete").click
@@ -191,7 +191,7 @@
# Cancel Confirmation
page.click_link("Cancel")
- expect(page).to have_current_path project_settings_project_storages_path(project)
+ expect(page).to have_current_path external_file_storages_project_settings_project_storages_path(project)
page.find(".icon.icon-delete").click
@@ -200,7 +200,7 @@
page.click_button("Delete")
# List of ProjectStorages empty again
- expect(page).to have_current_path project_settings_project_storages_path(project)
+ expect(page).to have_current_path external_file_storages_project_settings_project_storages_path(project)
expect(page).to have_text(I18n.t("storages.no_results"))
end
@@ -235,7 +235,7 @@
let!(:unconfigured_storage) { create(:nextcloud_storage) }
it "excludes storages that are not configured correctly" do
- visit project_settings_project_storages_path(project)
+ visit external_file_storages_project_settings_project_storages_path(project)
page.first(:link, 'New storage').click
diff --git a/modules/storages/spec/features/storages/project_settings/oauth_access_grant_spec.rb b/modules/storages/spec/features/storages/project_settings/oauth_access_grant_spec.rb
index 0f316692d790..2c07294ab8f0 100644
--- a/modules/storages/spec/features/storages/project_settings/oauth_access_grant_spec.rb
+++ b/modules/storages/spec/features/storages/project_settings/oauth_access_grant_spec.rb
@@ -65,7 +65,7 @@
end
it "adds a storage, nudges the project admin to grant OAuth access" do
- visit project_settings_project_storages_path(project_id: project)
+ visit external_file_storages_project_settings_project_storages_path(project_id: project)
click_on("Storage")
diff --git a/modules/storages/spec/features/storages_module_spec.rb b/modules/storages/spec/features/storages_module_spec.rb
deleted file mode 100644
index 0285741a3956..000000000000
--- a/modules/storages/spec/features/storages_module_spec.rb
+++ /dev/null
@@ -1,182 +0,0 @@
-# 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.
-#++
-
-require "spec_helper"
-require_module_spec_helper
-
-RSpec.describe "Storages module", :js, :with_cuprite do
- let(:permissions) do
- %i[manage_storages_in_project
- select_project_modules
- edit_project]
- end
- let(:user) { create(:admin) }
- let(:role) { create(:project_role, permissions:) }
- let(:storage) { create(:nextcloud_storage, name: "Storage 1") }
- let(:project) { create(:project, enabled_module_names: %i[storages work_package_tracking]) }
-
- current_user { user }
-
- shared_examples_for "content section has storages module" do
- it 'must show "storages" in content section' do
- within "#content" do
- text = I18n.t(:project_module_storages)
- expect(page).to have_text(text)
- end
- end
- end
-
- shared_examples_for "content section has storages permission header" do
- it 'must show "Automatically managed project folders" in content section' do
- within "#content" do
- expect(page).to have_text(I18n.t(:permission_header_for_project_module_storages).upcase)
- end
- end
- end
-
- shared_examples_for "sidebar has storages module" do
- it 'must show "storages" in sidebar' do
- within "#menu-sidebar" do
- expect(page).to have_text(I18n.t(:project_module_storages))
- end
- end
- end
-
- shared_examples_for "has storages module" do |sections: %i[content sidebar]|
- before { visit(path) }
-
- include_examples "content section has storages module" if sections.include?(:content)
- include_examples "sidebar has storages module" if sections.include?(:sidebar)
- end
-
- context "when in administration" do
- context "when showing index page" do
- it_behaves_like "has storages module" do
- let(:path) { admin_index_path }
- end
- end
-
- context "when showing system project settings page" do
- it_behaves_like "has storages module", sections: [:content] do
- let(:path) { admin_settings_new_project_path }
- end
- end
-
- context "when showing system storage settings page" do
- before { visit admin_settings_storages_path }
-
- it "must show the page" do
- expect(page).to have_text(I18n.t(:project_module_storages))
- end
- end
-
- context "when creating a new role" do
- before { visit new_role_path }
-
- include_examples "content section has storages permission header"
- end
-
- context "when editing a role" do
- before { visit edit_role_path(role) }
-
- include_examples "content section has storages permission header"
- end
-
- context "when showing the permissions report" do
- before { visit report_roles_path }
-
- include_examples "content section has storages permission header"
- end
- end
-
- context "when in project administration" do
- let(:user) { create(:user, member_with_permissions: { project => permissions }) }
-
- before do
- storage
- project
- end
-
- context "when showing the project module settings" do
- it_behaves_like "has storages module" do
- let(:path) { project_settings_modules_path(project) }
- end
- end
-
- context "when showing project storages settings page" do
- context "when storages module is enabled" do
- before do
- visit project_settings_project_storages_path(project)
- end
-
- it "must show the page" do
- expect(page).to have_text(I18n.t("project_module_storages"))
- end
- end
-
- context "when storages module is disabled" do
- let(:project) { create(:project, enabled_module_names: %i[work_package_tracking]) }
-
- context "when user has manage_storages_in_project permission" do
- it "must show the page and storage menu entry" do
- visit project_path(project)
- find("button.toggler.main-menu-toggler").click # opens project setting menu
-
- within "#menu-sidebar" do
- expect(page).to have_text(I18n.t(:project_module_storages))
- end
-
- visit project_settings_project_storages_path(project)
-
- expect(page).to have_text(I18n.t("project_module_storages"))
- end
- end
-
- context "when user has no manage_storages_in_project permission" do
- let(:permissions) { %i[select_project_modules edit_project] }
-
- it "must not show the page and storage menu entry" do
- visit project_path(project)
- find("button.toggler.main-menu-toggler").click # opens project setting menu
-
- within "#menu-sidebar" do
- expect(page).to have_no_text(I18n.t(:project_module_storages))
- end
-
- visit project_settings_project_storages_path(project)
-
- expect(page).to have_no_text(I18n.t("project_module_storages"))
- expect(page).to have_text("[Error 403] You are not authorized to access this page.")
- end
- end
- end
- end
- end
-end
diff --git a/modules/storages/spec/features/view_project_storage_members_spec.rb b/modules/storages/spec/features/view_project_storage_members_spec.rb
index 23ca7e34648f..62cf7b190511 100644
--- a/modules/storages/spec/features/view_project_storage_members_spec.rb
+++ b/modules/storages/spec/features/view_project_storage_members_spec.rb
@@ -60,7 +60,7 @@
login_as user
# Go to Projects -> Settings -> File Storages
- visit project_settings_project_storages_path(project)
+ visit external_file_storages_project_settings_project_storages_path(project)
expect(page).to have_title("Files")
expect(page).to have_text(storage.name)
@@ -90,7 +90,7 @@
project_storage_no_members = create(:project_storage, :as_automatically_managed, project: project_no_members, storage:)
# Go to Projects -> Settings -> File Storages
- visit project_settings_project_storages_path(project_no_members)
+ visit external_file_storages_project_settings_project_storages_path(project_no_members)
expect(page).to have_title("Files")
expect(page).to have_text(storage.name)
diff --git a/modules/storages/spec/requests/storages/project_settings/oauth_access_grant_flow_spec.rb b/modules/storages/spec/requests/storages/project_settings/oauth_access_grant_flow_spec.rb
index 41f5f901ede4..5578f632ee48 100644
--- a/modules/storages/spec/requests/storages/project_settings/oauth_access_grant_flow_spec.rb
+++ b/modules/storages/spec/requests/storages/project_settings/oauth_access_grant_flow_spec.rb
@@ -85,7 +85,7 @@
)
expect(last_response.cookies["oauth_state_#{nonce}"])
- .to eq([CGI.escape({ href: "http://example.org/projects/#{project.id}/settings/project_storages",
+ .to eq([CGI.escape({ href: "http://example.org/projects/#{project.id}/settings/project_storages/external_file_storages",
storageId: project_storage.storage_id }.to_json)])
end
end
@@ -105,7 +105,7 @@
storage.oauth_client
expect(last_response.status).to eq(302)
- expect(last_response.location).to eq("http://example.org/projects/#{project.id}/settings/project_storages")
+ expect(last_response.location).to eq("http://example.org/projects/#{project.id}/settings/project_storages/external_file_storages")
expect(last_response.cookies.keys).to eq(["_open_project_session"])
end
end
diff --git a/spec/features/work_packages/navigation_spec.rb b/spec/features/work_packages/navigation_spec.rb
index 059d7e3cae51..6364eed3f47c 100644
--- a/spec/features/work_packages/navigation_spec.rb
+++ b/spec/features/work_packages/navigation_spec.rb
@@ -159,7 +159,7 @@
it "access the work package views directly from a non-angular view" do
visit project_path(project)
- find("#main-menu-work-packages ~ .toggler").click
+ page.find_test_selector("main-menu-toggler--work_packages").click
expect(page).to have_css(".op-view-select--search-results")
find(".op-sidemenu--item-action", text: query.name).click
diff --git a/spec/lib/redmine/menu_manager/menu_helper_spec.rb b/spec/lib/redmine/menu_manager/menu_helper_spec.rb
index 7ae8ab7d26db..7c11f4072f0d 100644
--- a/spec/lib/redmine/menu_manager/menu_helper_spec.rb
+++ b/spec/lib/redmine/menu_manager/menu_helper_spec.rb
@@ -123,7 +123,7 @@ def @controller.current_menu_item
Parent node
-