diff --git a/app/seeders/standard.yml b/app/seeders/standard.yml index 9e2ca11be2f2..3922e8bacde8 100644 --- a/app/seeders/standard.yml +++ b/app/seeders/standard.yml @@ -91,6 +91,7 @@ projects: - board_view - team_planner_view - meetings + - documents news: - t_title: Welcome to your demo project t_summary: | @@ -389,6 +390,7 @@ projects: - work_package_tracking - gantt - board_view + - documents news: - t_title: Welcome to your Scrum demo project t_summary: | diff --git a/config/constants/settings/definition.rb b/config/constants/settings/definition.rb index b0253672993b..ae7fb0a9d919 100644 --- a/config/constants/settings/definition.rb +++ b/config/constants/settings/definition.rb @@ -346,7 +346,14 @@ class Definition allowed: -> { Redmine::I18n.all_languages } }, default_projects_modules: { - default: %w[calendar board_view work_package_tracking gantt news costs wiki], + default: -> { + base_modules = %w[calendar board_view work_package_tracking gantt news costs wiki] + if Setting.real_time_text_collaboration_enabled? + base_modules + %w[documents] + else + base_modules + end + }, allowed: -> { OpenProject::AccessControl.available_project_modules.map(&:to_s) } }, default_projects_public: { @@ -575,7 +582,10 @@ class Definition }, real_time_text_collaboration_enabled: { description: "Enable real-time collaborative editing of text fields using BlockNoteJS and Hocuspocus server.", - default: true + default: -> { + Setting.collaborative_editing_hocuspocus_url.present? && + Setting.collaborative_editing_hocuspocus_secret.present? + } }, collaborative_editing_hocuspocus_url: { format: :string, diff --git a/db/migrate/20260106151226_add_documents_to_default_projects_modules.rb b/db/migrate/20260106151226_add_documents_to_default_projects_modules.rb new file mode 100644 index 000000000000..a6eaf0a26291 --- /dev/null +++ b/db/migrate/20260106151226_add_documents_to_default_projects_modules.rb @@ -0,0 +1,49 @@ +# 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. +#++ + +class AddDocumentsToDefaultProjectsModules < ActiveRecord::Migration[8.0] + def up + return unless Setting.exists?(:real_time_text_collaboration_enabled) + return unless Setting.real_time_text_collaboration_enabled? + + # Only update if setting exists in DB (avoid updating on new installations - seeder handles that) + setting = Setting.find_by(name: "default_projects_modules") + return unless setting + + current_modules = setting.value || [] + return if current_modules.include?("documents") + + Setting.default_projects_modules = current_modules + ["documents"] + end + + def down + # No-op + end +end diff --git a/modules/bim/app/seeders/bim.yml b/modules/bim/app/seeders/bim.yml index b26e1f2c4cff..648ffa197b5a 100644 --- a/modules/bim/app/seeders/bim.yml +++ b/modules/bim/app/seeders/bim.yml @@ -247,6 +247,7 @@ projects: - wiki - board_view - team_planner_view + - documents news: - t_title: Welcome to your demo project t_summary: | @@ -395,6 +396,7 @@ projects: - wiki - board_view - team_planner_view + - documents news: - t_title: Welcome to your demo project t_summary: | diff --git a/modules/bim/spec/seeders/root_seeder_bim_edition_spec.rb b/modules/bim/spec/seeders/root_seeder_bim_edition_spec.rb index 2803933a8fa4..832a5f43a47b 100644 --- a/modules/bim/spec/seeders/root_seeder_bim_edition_spec.rb +++ b/modules/bim/spec/seeders/root_seeder_bim_edition_spec.rb @@ -53,7 +53,7 @@ def group_name(reference) it "creates the BIM demo data" do expect(Project.count).to eq 4 - expect(EnabledModule.count).to eq 27 + expect(EnabledModule.count).to eq 29 expect(WorkPackage.count).to eq 76 expect(Wiki.count).to eq 3 expect(Query.count).to eq 29 diff --git a/modules/documents/spec/controllers/documents_controller_spec.rb b/modules/documents/spec/controllers/documents_controller_spec.rb index 485d911a7626..ba1ee55edcd4 100644 --- a/modules/documents/spec/controllers/documents_controller_spec.rb +++ b/modules/documents/spec/controllers/documents_controller_spec.rb @@ -187,7 +187,10 @@ end describe "generate_oauth_token", - with_config: { collaborative_editing_hocuspocus_secret: "secret1234" } do + with_config: { + collaborative_editing_hocuspocus_url: "wss://hocuspocus.local", + collaborative_editing_hocuspocus_secret: "secret1234" + } do let(:manage_role) { create(:project_role, permissions: %i[view_documents manage_documents]) } let(:view_only_role) { create(:project_role, permissions: [:view_documents]) } let(:user_with_manage) { create(:user) } diff --git a/modules/documents/spec/features/attachment_upload_spec.rb b/modules/documents/spec/features/attachment_upload_spec.rb index a31a4b9898ea..9c3298987ac0 100644 --- a/modules/documents/spec/features/attachment_upload_spec.rb +++ b/modules/documents/spec/features/attachment_upload_spec.rb @@ -214,7 +214,7 @@ end end - context "for collaborative documents" do + context "for collaborative documents", with_settings: { real_time_text_collaboration_enabled: true } do let(:document) { create(:document, project:) } let(:editor) { FormFields::Primerized::BlockNoteEditorInput.new } let(:attachments_list) { Components::AttachmentsList.new } diff --git a/modules/documents/spec/features/block_note_editor_spec.rb b/modules/documents/spec/features/block_note_editor_spec.rb index a284f6f43d31..c99eb5d198c0 100644 --- a/modules/documents/spec/features/block_note_editor_spec.rb +++ b/modules/documents/spec/features/block_note_editor_spec.rb @@ -30,7 +30,7 @@ require "rails_helper" -RSpec.describe "BlockNote editor rendering", :js do +RSpec.describe "BlockNote editor rendering", :js, with_settings: { real_time_text_collaboration_enabled: true } do let(:admin) { create(:admin) } let(:type) { create(:document_type, :experimental) } let(:document) { create(:document, type:) } diff --git a/modules/documents/spec/features/documents/admin/settings/document_collaboration_settings_spec.rb b/modules/documents/spec/features/documents/admin/settings/document_collaboration_settings_spec.rb index b7d93dc15243..ee274a274d07 100644 --- a/modules/documents/spec/features/documents/admin/settings/document_collaboration_settings_spec.rb +++ b/modules/documents/spec/features/documents/admin/settings/document_collaboration_settings_spec.rb @@ -30,7 +30,9 @@ require "spec_helper" -RSpec.describe "Document collaboration settings admin", :js, :settings_reset do +RSpec.describe "Document collaboration settings admin", + :js, + :settings_reset do include Flash::Expectations current_user { create(:admin) } @@ -39,6 +41,16 @@ it "can configure hocuspocus url and secret" do visit admin_settings_document_collaboration_settings_path + within_test_selector("collaboration-settings-disabled-notice") do + expect(page).to have_heading("Real-time collaboration is not enabled") + expect(page).to have_content("Once enabled, multiple users will be able to work together on a " \ + "document at the same time. All new documents will be based on a new " \ + "editor (BlockNote) and will require a working connection to a Hocuspocus server.") + click_on "Enable real-time collaboration" + end + + expect_and_dismiss_flash(message: "Real-time collaboration has been enabled.") + expect(page).to have_field("Hocuspocus server URL", with: "") expect(page).to have_field("Client secret", with: "") @@ -72,22 +84,12 @@ end expect_and_dismiss_flash(message: "Real-time collaboration has been disabled.") - - within_test_selector("collaboration-settings-disabled-notice") do - expect(page).to have_heading("Real-time collaboration is not enabled") - expect(page).to have_content("Once enabled, multiple users will be able to work together on a " \ - "document at the same time. All new documents will be based on a new " \ - "editor (BlockNote) and will require a working connection to a Hocuspocus server.") - click_on "Enable real-time collaboration" - end - - expect_and_dismiss_flash(message: "Real-time collaboration has been enabled.") - expect(Setting.real_time_text_collaboration_enabled?).to be(true) end end context "with hocuspocus url set via environment variable", - with_env: { "OPENPROJECT_COLLABORATIVE_EDITING_HOCUSPOCUS_URL" => "wss://env-hocuspocus.example.com" } do + with_env: { "OPENPROJECT_COLLABORATIVE_EDITING_HOCUSPOCUS_URL" => "wss://env-hocuspocus.example.com" }, + with_settings: { collaborative_editing_hocuspocus_secret: "secret1234" } do before do reset(:collaborative_editing_hocuspocus_url) visit admin_settings_document_collaboration_settings_path @@ -99,11 +101,16 @@ expect(page).to have_field("Hocuspocus server URL", with: "wss://env-hocuspocus.example.com", disabled: true) + + expect(page).to have_field("Client secret", + with: "", + disabled: false) end end context "with secret set via environment variable", - with_env: { "OPENPROJECT_COLLABORATIVE_EDITING_HOCUSPOCUS_SECRET" => "envsupersecret" } do + with_env: { "OPENPROJECT_COLLABORATIVE_EDITING_HOCUSPOCUS_SECRET" => "envsupersecret" }, + with_settings: { collaborative_editing_hocuspocus_url: "wss://env-hocuspocus.example.com" } do before do reset(:collaborative_editing_hocuspocus_secret) visit admin_settings_document_collaboration_settings_path @@ -112,6 +119,9 @@ it "shows the secret as read-only" do expect(page).to have_content("Some values are configured via environment variables and cannot be edited here.") + expect(page).to have_field("Hocuspocus server URL", + with: "wss://env-hocuspocus.example.com", + disabled: false) expect(page).to have_field("Client secret", with: "", disabled: true) diff --git a/modules/documents/spec/features/documents/project/create_document_spec.rb b/modules/documents/spec/features/documents/project/create_document_spec.rb index 47a8518852f9..f7e4c1086aad 100644 --- a/modules/documents/spec/features/documents/project/create_document_spec.rb +++ b/modules/documents/spec/features/documents/project/create_document_spec.rb @@ -47,7 +47,7 @@ current_user { manager } - context "for collaborative documents" do + context "for collaborative documents", with_settings: { real_time_text_collaboration_enabled: true } do it "creates a new document via +Document buttons" do index_page.visit! diff --git a/modules/documents/spec/features/documents/project/show_edit_document_spec.rb b/modules/documents/spec/features/documents/project/show_edit_document_spec.rb index c29a83732ebf..d0a1fa8870f9 100644 --- a/modules/documents/spec/features/documents/project/show_edit_document_spec.rb +++ b/modules/documents/spec/features/documents/project/show_edit_document_spec.rb @@ -52,7 +52,8 @@ # rubocop:enable RSpec/AnyInstance end - it "renders a collaborative document" do + it "renders a collaborative document", + with_settings: { real_time_text_collaboration_enabled: true } do visit document_path(document) expect(page).to have_content("Collaborative document") diff --git a/spec/constants/settings/definition_spec.rb b/spec/constants/settings/definition_spec.rb index 4447e9f37a05..262dfa95d2f2 100644 --- a/spec/constants/settings/definition_spec.rb +++ b/spec/constants/settings/definition_spec.rb @@ -38,7 +38,7 @@ described_class.add_all - expect(described_class.all.keys).to eq(described_class::DEFINITIONS.keys) + expect(described_class.all.keys).to match_array(described_class::DEFINITIONS.keys) end it "does not add any plugin/feature settings if they were removed for some reason" do diff --git a/spec/migrations/add_documents_to_default_projects_modules_spec.rb b/spec/migrations/add_documents_to_default_projects_modules_spec.rb new file mode 100644 index 000000000000..14e7bf64b8d6 --- /dev/null +++ b/spec/migrations/add_documents_to_default_projects_modules_spec.rb @@ -0,0 +1,120 @@ +# 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. +#++ + +require "spec_helper" +require Rails.root.join("db/migrate/20260106151226_add_documents_to_default_projects_modules") + +RSpec.describe AddDocumentsToDefaultProjectsModules, type: :model do + let(:base_modules) { %w[calendar board_view work_package_tracking gantt news costs wiki] } + + before do + # Ensure a clean state + Setting.find_by(name: "default_projects_modules")&.destroy + Setting.clear_cache + end + + context "when real_time_text_collaboration is enabled", + with_settings: { real_time_text_collaboration_enabled: true } do + context "when default_projects_modules setting exists in DB" do + before do + Setting.default_projects_modules = base_modules + end + + it "adds documents to the default modules" do + ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } + + Setting.clear_cache + expect(Setting.default_projects_modules).to include("documents") + end + + it "preserves existing modules" do + ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } + + Setting.clear_cache + expect(Setting.default_projects_modules).to include(*base_modules) + end + + context "when documents is already in the default modules" do + before do + Setting.default_projects_modules = base_modules + ["documents"] + end + + it "does not duplicate documents" do + ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } + + Setting.clear_cache + expect(Setting.default_projects_modules.count("documents")).to eq(1) + end + end + end + + context "when default_projects_modules setting does not exist in DB" do + it "does not create the setting (seeder handles new installations)" do + expect(Setting.find_by(name: "default_projects_modules")).to be_nil + + ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } + + # Setting should still not exist - seeder will handle it + expect(Setting.find_by(name: "default_projects_modules")).to be_nil + end + end + end + + context "when real_time_text_collaboration is disabled", + with_settings: { real_time_text_collaboration_enabled: false } do + before do + Setting.default_projects_modules = base_modules + end + + it "does not modify the default modules" do + ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } + + Setting.clear_cache + expect(Setting.default_projects_modules).not_to include("documents") + expect(Setting.default_projects_modules).to match_array(base_modules) + end + end + + context "when real_time_text_collaboration_enabled setting does not exist" do + before do + Setting.default_projects_modules = base_modules + allow(Setting).to receive(:exists?).and_call_original + allow(Setting).to receive(:exists?).with(:real_time_text_collaboration_enabled).and_return(false) + end + + it "does not modify the default modules" do + ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } + + Setting.clear_cache + expect(Setting.default_projects_modules).not_to include("documents") + expect(Setting.default_projects_modules).to match_array(base_modules) + end + end +end diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb index c8ffa4ac1835..48e35f0333c0 100644 --- a/spec/models/setting_spec.rb +++ b/spec/models/setting_spec.rb @@ -603,4 +603,31 @@ end end end + + describe "default_projects_modules conditional default" do + shared_examples "base modules unchanged" do + it "includes the base modules" do + base_modules = %w[calendar board_view work_package_tracking gantt news costs wiki] + expect(Settings::Definition[:default_projects_modules].default).to include(*base_modules) + end + end + + context "when real_time_text_collaboration is enabled", + with_settings: { real_time_text_collaboration_enabled: true } do + it "includes documents in the default modules" do + expect(Settings::Definition[:default_projects_modules].default).to include("documents") + end + + it_behaves_like "base modules unchanged" + end + + context "when real_time_text_collaboration is disabled", + with_settings: { real_time_text_collaboration_enabled: false } do + it "does not include documents in the default modules" do + expect(Settings::Definition[:default_projects_modules].default).not_to include("documents") + end + + it_behaves_like "base modules unchanged" + end + end end diff --git a/spec/seeders/root_seeder_standard_edition_spec.rb b/spec/seeders/root_seeder_standard_edition_spec.rb index d3e7bd0f3eb8..326491fc2788 100644 --- a/spec/seeders/root_seeder_standard_edition_spec.rb +++ b/spec/seeders/root_seeder_standard_edition_spec.rb @@ -53,7 +53,7 @@ it "creates the demo data" do # rubocop:disable RSpec/MultipleExpectations expect(Project.count).to eq 2 - expect(EnabledModule.count).to eq 13 + expect(EnabledModule.count).to eq 15 expect(WorkPackage.count).to eq 36 expect(Wiki.count).to eq 2 expect(Query.having_views.count).to eq 8