diff --git a/app/contracts/custom_fields/hierarchy/update_item_contract.rb b/app/contracts/custom_fields/hierarchy/update_item_contract.rb index 01c12c873401..614cd121ab7e 100644 --- a/app/contracts/custom_fields/hierarchy/update_item_contract.rb +++ b/app/contracts/custom_fields/hierarchy/update_item_contract.rb @@ -52,6 +52,7 @@ class UpdateItemContract < Dry::Validation::Contract rule(:short) do next if schema_error?(:item) + next unless key? key.failure(:not_unique) if values[:item].siblings.exists?(short: value) end diff --git a/app/controllers/sys_controller.rb b/app/controllers/sys_controller.rb index b12087027fee..95e9132540a5 100644 --- a/app/controllers/sys_controller.rb +++ b/app/controllers/sys_controller.rb @@ -43,6 +43,24 @@ def repo_auth end end + def fetch_changesets + projects = [] + if params[:id] + projects << Project.active.has_module(:repository).find_by!(identifier: params[:id]) + else + projects = Project.active.has_module(:repository) + .includes(:repository).references(:repositories) + end + projects.each do |project| + if project.repository + project.repository.fetch_changesets + end + end + head :ok + rescue ActiveRecord::RecordNotFound + head :not_found + end + private def authorized?(project, user) diff --git a/config/routes.rb b/config/routes.rb index 38c278f2cdea..d8478eabfc6b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -704,8 +704,8 @@ scope controller: "sys" do match "/sys/repo_auth", action: "repo_auth", via: %i[get post] + get "/sys/fetch_changesets", action: "fetch_changesets" match "/sys/projects", to: proc { [410, {}, [""]] }, via: :all - match "/sys/fetch_changesets", to: proc { [410, {}, [""]] }, via: :all match "/sys/projects/:id/repository/update_storage", to: proc { [410, {}, [""]] }, via: :all end diff --git a/docs/release-notes/15-0-0/README.md b/docs/release-notes/15-0-0/README.md index 809db56fbba1..1b95ddf3bdd9 100644 --- a/docs/release-notes/15-0-0/README.md +++ b/docs/release-notes/15-0-0/README.md @@ -235,4 +235,4 @@ Last but not least, we are very grateful for our very engaged translation contri - [hmmftg](https://crowdin.com/profile/hmmftg), for a great number of translations into Persian. - [william](https://crowdin.com/profile/WilliamFromTW), for a great number of translations into Chinese Simplified and Chinese Traditional. -Would you like to help out with translations yourself? Then take a look at our [translation guide](../../contributions-guide/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! +Would you like to help out with translations yourself? Then take a look at our [translation guide](../../contributions-guide/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! \ No newline at end of file diff --git a/docs/release-notes/15-1-0/README.md b/docs/release-notes/15-1-0/README.md index ef71de6e2d42..f237f7392ead 100644 --- a/docs/release-notes/15-1-0/README.md +++ b/docs/release-notes/15-1-0/README.md @@ -3,25 +3,78 @@ title: OpenProject 15.1.0 sidebar_navigation: title: 15.1.0 release_version: 15.1.0 -release_date: 2024-11-28 +release_date: 2024-12-11 --- # OpenProject 15.1.0 -Release date: 2024-11-28 +Release date: 2024-12-11 -We released OpenProject [OpenProject 15.1.0](https://community.openproject.org/versions/2122). -The release contains several bug fixes and we recommend updating to the newest version. -In these Release Notes, we will give an overview of important feature changes. -At the end, you will find a complete list of all changes and bug fixes. +We released [OpenProject 15.1.0](https://community.openproject.org/versions/2122). The release contains several bug fixes, and we recommend updating to the newest version. +In these Release Notes, we will give an overview of important feature changes and technical updates. At the end, you will find a complete list of all changes and bug fixes. ## Important feature changes - +### Custom fields of type hierarchy (Enterprise add-on) -## Important updates and breaking changes +Enterprise customers can now use a new type of custom field that allows **multi-level selections**. This makes it easier for users to organize and navigate complex data in structured, multi-level formats within work packages. The new custom fields of the hierarchy type can be added to work packages and then structured into several lower-level values. - +Each custom field of type hierarchy can be given a short name (e.g. B for Berlin). Here's an example of how custom fields of the hierarchy type look like, using the example of a detailed assignment of workspaces: + +![Example screenshot of custom fields of type hiearchy, displaying different cities as main offices](openproject-15-1-custom-field-hierarchy.jpg) + +[Read all about custom fields in our system admin guide](../../system-admin-guide/custom-fields/). + +### Redesign of the Relations tab in work packages + +The Relations tab in work packages has been completely redesigned using Primer design system, including a **new dropdown menu that allows you to directly choose the type of relation**, e.g. if the related work package is a successor and necessarily needs to start after the selected one finishes. + +Additionally, you can now add a description to add further information about the relation. Please note that the description will be displayed on both work packages, below the related other work package. + +> [!NOTE] +> Important information: With this redesign, **you will no longer be able to create new work packages directly on the Relations tab**. Please tell us if you were using this feature a lot. If it will be missed by many users, we will find a way to bring it back. + +![Screenshot showing the new Relations tab in a work package](openproject-15-1-relations.png) + +[Read all about work package relations and hierarchies in our user guide](../../user-guide/work-packages/work-package-relations-hierarchies/). + +### Redesign of the Meetings index page + +The index page of the Meetings module has been redesigned with Primer as well, making it easier to read and have a more modern look. You see your list of meetings in some kind of table view, with the columns being: Title, Date and time, Duration, and Location. + +The + Meeting button in the top right corner now offers a dropdown menu where you can directly choose whether you want to add a dynamic or classic meeting. + +Here's an example screenshot of the redesigned Meetings index page: + +![Example screenshot of the redesigned Meetings index page](openproject-15-1-meetings.png) + +[Learn what is possible with OpenProject's Dynamic Meetings to improve collaboration with your colleagues](../../user-guide/meetings/dynamic-meetings/). + +### Manual page breaks in PDF work package exports + +With our work package export feature, people can generate good-looking PDFs. Sometimes, however, the page break comes at an inconvenient place. With version 15.1, users can now force a manual page break in the work package description. This ensures, for example, that a signature can always be inserted on the correct page. + +![Example of a work package description with an employee conctract and inserted page breaks](openproject-15-1-page-break-contract-highlighted.png) + +[Learn how to export work packages and what options you have](../../user-guide/work-packages/exporting/). + +### Zen mode for project lists + +Zen mode allows users to focus on a certain page, as all other menu items and elements are hidden, and the page is displayed in full screen. OpenProject already offers zen mode for other modules like Work packages, Boards, Gantt charts or Calendar – and with version 15.1 also for project lists. + +Here is how zen mode for project lists looks like: + +![Example screenshot of a project list in zen mode](openproject-15-1-zen-mode-highlighted.png) + +[Read all about OpenProject's project lists in our user guide](../../user-guide/projects/project-lists/). + +## Important technical updates + +### Possibility to lock seeded admin users, e.g. when using LDAP + +Administrators of automated deployments can now choose to skip the automatically integrated creation of an admin user. This is useful if you have set up an LDAP or SSO integration – such as those used for openDesk environments – and you want to prevent the admin user from logging in. Administrators no longer have to manually disable this automatically created admin user and thus run the risk of forgetting to do so, which would pose a security risk. + +Read more about [seeding through environment for OpenProject configuration in our Installation & operations guide](../../installation-and-operations/configuration/#seeding-through-environment) @@ -68,12 +121,14 @@ At the end, you will find a complete list of all changes and bug fixes. ## Contributions -A very special thank you goes to our sponsors for this release. -Also a big thanks to our Community members for reporting bugs and helping us identify and provide fixes. -Special thanks for reporting and finding bugs go to Frank Long, Claudio Pagnani, Ivan Kuchin, samuel law, Gerrit B.. -Last but not least, we are very grateful for our very engaged translation contributors on Crowdin, who translated quite a few OpenProject strings! -Would you like to help out with translations yourself? -Then take a look at our translation guide and find out exactly how you can contribute. -It is very much appreciated! +A very special thank you goes to our sponsors of this release: Deutsche Bahn for sponsoring custom fields of type hierarchy, and City of Cologne for sponsoring custom fields of type hierarchy as well as zen mode for project lists. + +Also, a big thanks to our Community members for reporting bugs and helping us identify and provide fixes. Special thanks for reporting and finding bugs go to Bill Bai, Sam Yelman, Knight Chang, Gábor Alexovics, Gregor Buergisser, Andrey Dermeyko, Various Interactive, Clayton Belcher, Александр Татаринцев, and Keno Krewer. + +Last but not least, we are very grateful for our very engaged translation contributors on Crowdin, who translated quite a few OpenProject strings! This release we would like to highlight +- [José Helbert Pina](https://crowdin.com/profile/GZTranslations), for a great number of translations into Portuguese. +- [Alexander Aleschenko](https://crowdin.com/profile/top4ek), for a great number of translations into Russian. +- [Adam Siemienski](https://crowdin.com/profile/siemienas), for a great number of translations into Polish. +Would you like to help out with translations yourself? Then take a look at our [translation guide](../../contributions-guide/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! \ No newline at end of file diff --git a/docs/release-notes/15-1-0/openproject-15-1-custom-field-hierarchy.jpg b/docs/release-notes/15-1-0/openproject-15-1-custom-field-hierarchy.jpg new file mode 100644 index 000000000000..d664b23c5305 Binary files /dev/null and b/docs/release-notes/15-1-0/openproject-15-1-custom-field-hierarchy.jpg differ diff --git a/docs/release-notes/15-1-0/openproject-15-1-meetings.png b/docs/release-notes/15-1-0/openproject-15-1-meetings.png new file mode 100644 index 000000000000..2cfd649e6133 Binary files /dev/null and b/docs/release-notes/15-1-0/openproject-15-1-meetings.png differ diff --git a/docs/release-notes/15-1-0/openproject-15-1-page-break-contract-highlighted.png b/docs/release-notes/15-1-0/openproject-15-1-page-break-contract-highlighted.png new file mode 100644 index 000000000000..0217b2eb9cd6 Binary files /dev/null and b/docs/release-notes/15-1-0/openproject-15-1-page-break-contract-highlighted.png differ diff --git a/docs/release-notes/15-1-0/openproject-15-1-relations.png b/docs/release-notes/15-1-0/openproject-15-1-relations.png new file mode 100644 index 000000000000..405e60dbfa8b Binary files /dev/null and b/docs/release-notes/15-1-0/openproject-15-1-relations.png differ diff --git a/docs/release-notes/15-1-0/openproject-15-1-zen-mode-highlighted.png b/docs/release-notes/15-1-0/openproject-15-1-zen-mode-highlighted.png new file mode 100644 index 000000000000..ea7495f3ad77 Binary files /dev/null and b/docs/release-notes/15-1-0/openproject-15-1-zen-mode-highlighted.png differ diff --git a/spec/contracts/custom_fields/hierarchy/update_item_contract_spec.rb b/spec/contracts/custom_fields/hierarchy/update_item_contract_spec.rb index d1f0a7ebeaf9..f2eaae17f3c2 100644 --- a/spec/contracts/custom_fields/hierarchy/update_item_contract_spec.rb +++ b/spec/contracts/custom_fields/hierarchy/update_item_contract_spec.rb @@ -38,6 +38,7 @@ let!(:vader) { create(:hierarchy_item) } let!(:luke) { create(:hierarchy_item, label: "luke", short: "ls", parent: vader) } let!(:leia) { create(:hierarchy_item, label: "leia", short: "lo", parent: vader) } + let!(:starkiller) { create(:hierarchy_item, label: "starkiller", parent: vader) } context "when all required fields are valid" do it "is valid" do diff --git a/spec/controllers/sys_controller_spec.rb b/spec/controllers/sys_controller_spec.rb index 00b3080e3c66..f5db5870a7f6 100644 --- a/spec/controllers/sys_controller_spec.rb +++ b/spec/controllers/sys_controller_spec.rb @@ -493,4 +493,52 @@ end end end + + describe "#fetch_changesets" do + let(:params) { { id: repository_project.identifier } } + + before do + request.env["HTTP_AUTHORIZATION"] = + ActionController::HttpAuthentication::Basic.encode_credentials( + valid_user.login, + valid_user_password + ) + + allow_any_instance_of(Repository::Subversion).to receive(:fetch_changesets).and_return(true) + + get "fetch_changesets", params: params.merge({ key: api_key }) + end + + context "with a project identifier" do + it "is successful" do + expect(response) + .to have_http_status(:ok) + end + end + + context "without a project identifier" do + let(:params) { {} } + + it "is successful" do + expect(response) + .to have_http_status(:ok) + end + end + + context "for an unknown project" do + let(:params) { { id: 0 } } + + it "returns 404" do + expect(response) + .to have_http_status(:not_found) + end + end + + context "when disabled", with_settings: { sys_api_enabled?: false } do + it "is 403 forbidden" do + expect(response) + .to have_http_status(:forbidden) + end + end + end end diff --git a/spec/factories/hierarchy_item_factory.rb b/spec/factories/hierarchy_item_factory.rb index c24c0155d161..0185ff0f3feb 100644 --- a/spec/factories/hierarchy_item_factory.rb +++ b/spec/factories/hierarchy_item_factory.rb @@ -31,6 +31,5 @@ FactoryBot.define do factory :hierarchy_item, class: "CustomField::Hierarchy::Item" do sequence(:label) { |n| "Item #{n}" } - sequence(:short) { |n| "I #{n}" } end end diff --git a/spec/features/work_packages/details/relations/primerized_relations_spec.rb b/spec/features/work_packages/details/relations/primerized_relations_spec.rb index f80169512089..7160370fd0e1 100644 --- a/spec/features/work_packages/details/relations/primerized_relations_spec.rb +++ b/spec/features/work_packages/details/relations/primerized_relations_spec.rb @@ -106,6 +106,9 @@ def label_for_relation_type(relation_type) describe "rendering" do it "renders the relations tab" do scroll_to_element relations_panel + + wait_for_network_idle + expect(page).to have_css(relations_panel_selector) tabs.expect_counter("relations", 4) @@ -120,6 +123,8 @@ def label_for_relation_type(relation_type) it "can delete relations" do scroll_to_element relations_panel + wait_for_network_idle + relations_tab.remove_relation(relation_follows) expect { relation_follows.reload }.to raise_error(ActiveRecord::RecordNotFound) @@ -130,6 +135,8 @@ def label_for_relation_type(relation_type) it "can delete children" do scroll_to_element relations_panel + wait_for_network_idle + relations_tab.remove_child(child_wp) expect(child_wp.reload.parent).to be_nil @@ -141,6 +148,8 @@ def label_for_relation_type(relation_type) it "renders an edit form" do scroll_to_element relations_panel + wait_for_network_idle + relation_row = relations_tab.expect_relation(relation_follows) relations_tab.edit_relation_description(relation_follows, "Discovered relations have descriptions!") @@ -167,6 +176,8 @@ def label_for_relation_type(relation_type) it "does not have an edit action for children" do scroll_to_element relations_panel + wait_for_network_idle + child_row = relations_panel.find("[data-test-selector='op-relation-row-#{child_wp.id}']") within(child_row) do @@ -196,10 +207,18 @@ def label_for_relation_type(relation_type) relation_type: Relation::TYPE_FOLLOWS) end + before do + another_wp + relation_to + end + it "shows the correct related WorkPackage in the dialog (regression #59771)" do scroll_to_element relations_panel - relations_tab.open_relation_dialog(relation_to) + wait_for_network_idle + + relations_tab.expect_relation(another_wp) + relations_tab.open_relation_dialog(another_wp) within "##{WorkPackageRelationsTab::WorkPackageRelationDialogComponent::DIALOG_ID}" do expect(page).to have_field("Work package", @@ -216,6 +235,8 @@ def label_for_relation_type(relation_type) it "renders the new relation form for the selected type and creates the relation" do scroll_to_element relations_panel + wait_for_network_idle + relations_tab.add_relation(type: :precedes, relatable: wp_successor, description: "Discovered relations have descriptions!") relations_tab.expect_relation(wp_successor) @@ -252,6 +273,8 @@ def label_for_relation_type(relation_type) it "renders the new child form and creates the child relationship" do scroll_to_element relations_panel + wait_for_network_idle + tabs.expect_counter("relations", 4) relations_tab.add_existing_child(not_yet_child_wp) @@ -310,6 +333,8 @@ def label_for_relation_type(relation_type) it "does not show options to add or edit relations" do scroll_to_element relations_panel + wait_for_network_idle + tabs.expect_counter("relations", 4) relations_tab.expect_no_add_relation_button @@ -325,6 +350,8 @@ def label_for_relation_type(relation_type) it "does not show the option to delete the child" do scroll_to_element relations_panel + wait_for_network_idle + tabs.expect_counter("relations", 4) # The menu is shown as the user can add a relation @@ -346,6 +373,8 @@ def label_for_relation_type(relation_type) it "does not show the option to edit the relation but only the child" do scroll_to_element relations_panel + wait_for_network_idle + tabs.expect_counter("relations", 4) # The menu is shown as the user can add a child diff --git a/spec/support/components/work_packages/relations.rb b/spec/support/components/work_packages/relations.rb index 69ca5493c2aa..d2d22be2a2b8 100644 --- a/spec/support/components/work_packages/relations.rb +++ b/spec/support/components/work_packages/relations.rb @@ -64,11 +64,11 @@ def expect_no_add_relation_button def find_row(relatable) actual_relatable = find_relatable(relatable) - page.find_test_selector("op-relation-row-#{actual_relatable.id}") + page.find_test_selector("op-relation-row-#{actual_relatable.id}", wait: 5) end def find_some_row(text:) - page.find("[data-test-selector^='op-relation-row']", text:) + page.find("[data-test-selector^='op-relation-row']", text:, wait: 5) end def expect_row(work_package)