Merge branch 'release/16.0' into dev

This commit is contained in:
OpenProject Actions CI
2025-05-12 08:42:25 +00:00
41 changed files with 321 additions and 165 deletions
+4 -3
View File
@@ -50,7 +50,7 @@ class WorkPackages::DeleteService < BaseServices::Delete
def destroy_descendants(descendants, result)
descendants.each do |descendant|
success = destroy(descendant)
success = destroy(descendant.reload)
result.add_dependent!(ServiceResult.new(success:, result: descendant))
end
end
@@ -63,6 +63,7 @@ class WorkPackages::DeleteService < BaseServices::Delete
def find_successors_of_self_and_descendants(work_package)
WorkPackage.where(id: Relation.follows.of_predecessor(work_package.self_and_descendants).select(:from_id))
.where.not(id: work_package.self_and_descendants)
end
def update_ancestors_and_successors(successors, result)
@@ -94,8 +95,8 @@ class WorkPackages::DeleteService < BaseServices::Delete
.new(user:, work_package: work_packages_to_reschedule)
.call
result.dependent_results.map(&:result).each do |dependent_result|
dependent_result.save(validate: false)
result.dependent_results.map(&:result).each do |rescheduled_work_package|
rescheduled_work_package.save(validate: false)
end
result
@@ -320,7 +320,7 @@ OPENPROJECT_SEED_DESIGN_EXPORT__COVER="..."
### Allowing public access
By default, any request to the OpenProject application needs to be authenticated. If you want to enable public unauthenticated access like we do for https://community.openproject.org, you can set the `login_required` to `false`. If not provided through environment variables, this setting is also accessible in the administrative UI. Please see the [authentication settings guide](../../system-admin-guide/authentication/authentication-settings/#general-authentication-settings) for more details.
By default, any request to the OpenProject application needs to be authenticated. If you want to enable public unauthenticated access like we do for https://community.openproject.org, you can set the `login_required` to `false`. If not provided through environment variables, this setting is also accessible in the administrative UI. Please see the [authentication settings guide](../../system-admin-guide/authentication/login-registration-settings/) for more details.
*default: true*
@@ -13,12 +13,12 @@ Configure **authentication** settings and authentication providers in OpenProjec
## Overview
| Topic | Content |
|------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------|
| [Authentication settings](authentication-settings) | Configure general authentication settings, such as registration, passwords, and more. |
| [OAuth applications](oauth-applications) | How to configure OAuth applications in OpenProject. |
| [OpenID providers](openid-providers) | How to configure OpenID providers in OpenProject. |
| [Two-factor authentication](two-factor-authentication) | Set up and manage two-factor authentication (2FA) in OpenProject. |
| [reCAPTCHA](recaptcha) | How to activate reCAPTCHA in OpenProject. |
| [LDAP authentication](ldap-connections) | How to set up LDAP authentication in OpenProject. |
| [LDAP group synchronization](ldap-connections/ldap-group-synchronization) | How to configure LDAP group synchronization in OpenProject. (Enterprise add-on) |
| Topic | Content |
| ------------------------------------------------------------ | :----------------------------------------------------------- |
| [Login and registration settings](login-registration-settings) | Configure general authentication settings, such as registration, SSO, passwords, and more. |
| [OAuth applications](oauth-applications) | How to configure OAuth applications in OpenProject. |
| [OpenID providers](openid-providers) | How to configure OpenID providers in OpenProject. |
| [Two-factor authentication](two-factor-authentication) | Set up and manage two-factor authentication (2FA) in OpenProject. |
| [reCAPTCHA](recaptcha) | How to activate reCAPTCHA in OpenProject. |
| [LDAP authentication](ldap-connections) | How to set up LDAP authentication in OpenProject. |
| [LDAP group synchronization](ldap-connections/ldap-group-synchronization) | How to configure LDAP group synchronization in OpenProject. (Enterprise add-on) |
@@ -22,7 +22,7 @@ For on premises installations the functionality can be deactivated the same way
## Can we ensure that passwords are secure / have a high strength?
Password parameters for OpenProject can be configured on each OpenProject environment. Typically passwords require 10+ characters, as well as special characters. Please find the respective instruction [here](../authentication-settings/#configure-password-settings).
Password parameters for OpenProject can be configured on each OpenProject environment. Typically passwords require 10+ characters, as well as special characters. Please find the respective instruction [here](../login-registration-settings/#password-settings).
## How can a user change his/her authentication method?
@@ -1,73 +0,0 @@
---
sidebar_navigation:
title: Settings
priority: 990
description: Authentication settings in OpenProject.
keywords: authentication settings
---
# Authentication settings
To adapt general system **authentication settings**, navigate to *Administration -> Authentication* and choose -> *Authentication Settings*.
You can adapt the following under the authentication settings:
## General authentication settings
1. Select if the **authentication is required** to access OpenProject. For versions 13.1 and higher of OpenProject, this setting will be checked by default
> [!IMPORTANT]
> If you un-tick this box your OpenProject instance will be visible to the general public without logging in. The visibility of individual projects depends on [this setting](../../../user-guide/projects/#set-a-project-to-public).
2. Select an option for **self-registration**. Self-registration can either be **disabled**, or it can be allowed with the following criteria:
a) **Account activation by email** means the user receives an email and needs to confirm the activation.
b) **Manual account activation** means that a system administrator needs to manually activate the newly registered user.
c) **Automatic account activation** means that a newly registered user will automatically be active.
> [!NOTE]
> By default, self-registration is only applied to internal users (logging in with username and password). If you have an identity provider such as LDAP, SAML or OpenID Connect, use the respective settings in their configuration to control which users are applicable for automatic user creation.
3. Define if the **email address should be used as login** name.
4. Define after how many days the **activation email sent to new users will expire**. Afterwards, you will have the possibility to [re-send the activation email](../../users-permissions/users/#resend-user-invitation-via-email) via the user settings.
![Authentication settings in OpenProject system administration](openproject_system_admin_guide_authentication_settings.png)
## Define a registration footer for registration emails
You can define a footer for your registration emails under -> *Administration* -> *Authentication* -> *Authentication Settings*.
1. Choose for which **language** you want to define the registration footer.
2. Enter a **text for the registration footer**.
![Define registration footer for registration emails in OpenProject administration](openproject_system_admin_guide_authentication_settings_registration_footer.png)
## Configure password settings
You can change various settings to configure password preferences in OpenProject.
1. Define the **minimum password length**.
2. Define the password strength and select what **character classes are a mandatory part of the password**.
3. Define the **minimum number of required character classes**.
4. Define the number of days, after which a **password change should be enforced**.
5. Define the **number of the most recently used passwords that a user should not be allowed to reuse**.
6. Activate the **Forgot your password.** This way a user will be able to reset the own password via email.
![Password settings in OpenProject administration](openproject_system_admin_guide_authentication_settings_passwords.png)
## Other authentication settings
There can be defined a number of other authentication settings.
1. Define the number of failed **login attempts, after which a user will be temporarily blocked**.
2. Define the **duration of the time, for which the user will be blocked after failed login attempts**.
3. Enable or disable the **autologin option**. This allows a user to remain logged in, even if he/she leaves the site. If this option is activated, the “Stay signed in” option will appear on the login screen to be selected.
4. Activate the **session expiration option**. If you select this option, an additional field will open, where you will be able to define the **inactivity time duration before the session expiry**.
5. Define to **log user login, name, and mail address for all requests**.
6. Define a path to redirect users to after their first login. If left empty, users are redirected to the homepage to see the onboarding tour.
7. Set a default path to redirect users after login (only if the login link is not a back link, i.e. www.example.openproject.com/login). If left empty, users are redirected to the homepage.
8. Do not forget to **save** your changes.
![Additional authentication settings in OpenProject administration](openproject_system_admin_guide_authentication_settings_other.png)
@@ -0,0 +1,97 @@
---
sidebar_navigation:
title: Settings
priority: 990
description: Login and registration settings in OpenProject.
keywords: authentication settings, login settings, registration settings, OpenProject login, login, registration
---
# Login and registration
To adapt general system **login and registration settings** in OpenProject, navigate to *Administration -> Authentication* and choose -> *Login and registration*.
Here you can adapt various settings related to login and registration in OpenProject, grouped under three tabs:
- Login and SSO
- Registration
- Passwords
![Login and registration settings in OpenProject administration](openproject_system_admin_guide_authentication_settings_login_and_registration.png)
## Login and SSO settings
Under the *Login and SSO* tab you can adjust following settings:
1. Select a **direct login SSO provider**. If this option is active, login requests will be redirected to the configured Omniauth provider. This will disable the login dropdown and sign-in page.
> [!NOTE]
>
> Unless you also disable password logins, with this option enabled, users can still log in internally by visiting internal login page, for example `https://yourinstancename.openproject.com/login/internal` login page.
2. Enable or disable the **autologin option**. This allows a user to remain logged in, even if they leave the site. If this option is activated, the “Stay signed in” option will appear on the login screen to be selected.
3. Activate the **session expiration option**.
4. Set the **duration for inactivity time**, after which a session will expire. Note that any value below 5 will be treated as disabling the session expiry setting.
5. Define whether **user login, name, and mail address** should be logged for all requests.
6. Define a path to **redirect users to after their first login**. If left empty, users are redirected to the homepage to see the onboarding tour.
7. Set a **default path to redirect users to after login** (only if the login link is not a back link, i.e. `www.example.openproject.com/login`). If left empty, users are redirected to the homepage.
Do not forget to **save** your changes.
![Login and SSO tab under login and registration settings in OpenProject system administration](openproject_system_admin_guide_authentication_settings_login_sso_tab.png)
## Registration settings
Under the *Registration* tab you can adjust following settings:
1. Select if the **authentication is required** to access OpenProject. For versions 13.1 and higher of OpenProject, this setting will be checked by default
> [!IMPORTANT]
> If you uncheck this box, your OpenProject instance will be visible to the general public without logging in. The visibility of individual projects depends on [this setting](../../../user-guide/projects/#set-a-project-to-public).
2. Select an option for **self-registration**. Self-registration can either be **disabled**, or it can be allowed with the following criteria:
a) **Account activation by email** - users can register on their own. They will receive an activation email and will need to activate their account after confirming their email address.
> [!WARNING]
>
> Administrators have no moderation control over this activation process if this method is selected.
b) **Manual account activation** - users can register on their own. However, an administrator (or a user with the global permission to create or manage users) needs to activate them.
c) **Automatic account activation** - users can register on their own. Their accounts are immediately active without further action.
> [!WARNING]
>
> Administrators have no moderation control over this activation process if this method is selected.
> [!NOTE]
> By default, self-registration is only applied to internal users (logging in with username and password). If you have an identity provider such as LDAP, SAML or OpenID Connect, use the respective settings in their configuration to control which users are applicable for automatic user creation.
3. Define after how many days the **activation email sent to new users will expire**. Afterwards, you will have the possibility to [re-send the activation email](../../users-permissions/users/#resend-user-invitation-via-email) via the user settings.
3. Choose for which **language** you want to define **the footer displayed at the bottom of the registration page** and formulate that footer text.
![Registration tab under Login and Registration section of authentication settings in OpenProject administration](openproject_system_admin_guide_authentication_settings_registration_tab.png)
## Password settings
Under the *Password* tab you can adjust following settings:
1. Define the **minimum password length**.
2. Select what **character classes are a mandatory part of the password**.
3. Define the **minimum number of required character classes**.
4. Define the number of days, after which a **password change should be enforced**. Value of 0 disables this option, i.e. no password change will be enforced.
5. Define the **number of the most recently used passwords that a user should not be allowed to reuse**.
6. Activate the **password reset** (Forgot your password option). This way users will be able to reset their own passwords via email.
7. Define the number of failed **login attempts, after which a user will be temporarily blocked**. Value of 0 disables this option, i.e. users will not be blocked after any amount of failed login attempts.
8. Define the **duration of the time, for which the user will be blocked after failed login attempts**. Value of 0 disables this option.
![Password settings tab under Login and Registration section of authentication settings in OpenProject administration](openproject_system_admin_guide_authentication_settings_password_tab.png)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 82 KiB

@@ -250,7 +250,7 @@ OPENPROJECT_SAML_SAML_SECURITY_DIGEST__METHOD="http://www.w3.org/2000/09/xmldsig
#### Optional: Restrict who can automatically self-register
You can configure OpenProject to restrict which users can register on the system with the [authentication self-registration setting](../authentication-settings)
You can configure OpenProject to restrict which users can register on the system with the [authentication self-registration setting](../login-registration-settings/)
By default, users returning from a SAML idP will be automatically created. If you'd like for the SAML integration to respect the configured self-registration option, please use this setting:
@@ -304,7 +304,7 @@ When you return from the authentication provider, you might be shown one of thes
Once created, you can assign this SAML provider to become the direct login provider. Users will be directed to the login page of the provider without seeing a login form in OpenProject. [Read more](../../../installation-and-operations/configuration/#omniauth-direct-login-provider).
In the user interface, you can assign this through [Administration > Authentication > Settings](../authentication-settings/).
In the user interface, you can assign this through [Administration > Authentication > Settings](../login-registration-settings/).
Using environment variables, you could also set this in the following way
@@ -16,12 +16,27 @@ storages*. In addition, administrator can manually trigger a connection validat
### Connection validation for OneDrive/SharePoint
Every file storage for OneDrive/SharePoint has the ability to run a connection test. This test is triggered manually by
clicking on **Recheck connection** in the sidebar on the right side of the file storage's details view. This check is
available after the file storage is fully configured.
Every file storage for OneDrive/SharePoint has the ability to run connection checks. This test is triggered manually by clicking on **Run checks now** in the sidebar on the right side of the file storage's details view. This check is available from the UI to edit the storage.
![Recheck connection for OneDrive/SharePoint in OpenProject administration](openproject_file_storages_recheck_connection.png)
![Recheck connection for OneDrive/SharePoint in OpenProject administration](openproject_file_storages_onedrive_run_checks_button.png)
### Health report for OneDrive/SharePoint
Once the check is finished, a full health report will be generated. Click **open full health report** to see the report in full detail and to download it.
![Link to open full health report for OneDrive/SharePoint file storage in OpenProject administration](openproject_file_storages_onedrive_open_full_health_report.png)
Full health status report will give an overview of all checks that were performed grouped into three categories:
- Basic configuration
- Authentication
- Automatically managed project folder configuration (if activated for this file storage)
In the top right corner you can **Re-run all checks** or **download the health (text format)** report by clicking respective buttons.
![Health status report for OneDrive/SharePoint integration in OpenProject administration](openproject_file_storages_onedrive_open_full_health_report_download_button.png)
### Error codes
There are several possible errors that can occur during the connection test. The following table lists the error codes with a description of the possible reasons and suggested solutions.
| Error code | Error description | Possible reasons | Next steps and solutions |
@@ -35,11 +50,27 @@ There are several possible errors that can occur during the connection test. The
### Connection validation for Nextcloud
Same as OneDrive/SharePoint, every file storage for Nextcloud has the ability to run a connection test. This test is
triggered manually by clicking on **Recheck connection** in the sidebar on the right side of the file storage's details
view. This check is available after the file storage is fully configured.
Same as OneDrive/SharePoint, every file storage for Nextcloud has the ability to run a connection test. This test is triggered manually by clicking on **Run checks now** in the sidebar on the right side of the file storage's details view. This check is available from the UI to edit the storage.
![Recheck connection for Nextcloud in OpenProject administration](openproject_file_storages_recheck_connection_nextcloud.png)
![Recheck connection for Nextcloud in OpenProject administration](openproject_file_storages_nextcloud_run_checks_link.png)
### Health report for Nextcloud
Once the check is finished, a full health report will be generated. Click **open full health report** to see the report in full detail and to download it.
![Link to open full health report for Nexctloud file storage in OpenProject administration](openproject_file_storages_nextcloud_open_full_health_report_button.png)
Full health status report will give an overview of all checks that were performed grouped into three categories:
- Basic configuration
- Authentication
- Automatically managed project folder configuration (if activated for this file storage)
In the top right corner you can **Re-run all checks** or **download the health (text format)** report by clicking respective buttons.
![Health status report for Nextcloud integration in OpenProject administration](openproject_file_storages_nextcloud_open_full_health_report_download_button.png)
### Nextcloud integration error codes
There are several possible errors that can occur during the connection test. The following table lists the error codes
with a description of the possible reasons and suggested solutions.
@@ -57,9 +88,7 @@ the [system admin guide](../../../../system-admin-guide/integrations/nextcloud/#
## Health checks for automatically managed project folders
File storages with the *Automatically managed project folders* option will have reoccurring synchronization
runs, that update the user permissions on the external system and report possible errors. An additional section is
displayed for those file storages in the side bar.
File storages with the *Automatically managed project folders* option will have reoccurring synchronization runs, that update the user permissions on the external system and report possible errors. An additional section is displayed for those file storages in the side bar.
![Health check for automatically managed folders in file storage integrations in OpenProject](openproject_file_storages_health_message.png)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 169 KiB

@@ -55,7 +55,7 @@ If you are using [Enterprise cloud](../../../enterprise-guide/enterprise-cloud-g
![Lock users in OpenProject](open_project_system_admin_lock_user_permanently.png)
If a user has repeated failed logins the user will be locked temporarily and a **Reset failed logins** link will be shown in the user list. Click the link to unlock it right away, or wait and it will be unlocked automatically. Have a look at the section [Other authentication settings](../../authentication/authentication-settings/#other-authentication-settings) for failed attempts and time blocked.
If a user has repeated failed logins the user will be locked temporarily and a **Reset failed logins** link will be shown in the user list. Click the link to unlock it right away, or wait and it will be unlocked automatically. Have a look at the section [Other authentication settings](../../authentication/login-registration-settings/) for failed attempts and time blocked.
## Create users
@@ -76,9 +76,9 @@ When adding the last of multiple users you can click on **Create** or click the
### Create user (via self-registration)
To allow users to create their own user accounts enable self-registration in the [authentication settings](../../authentication/authentication-settings). A person can then create their own user from the home page by clicking on the **Sign in** button (top right), then on the **Create a new account** link in the sign in box.
To allow users to create their own user accounts enable self-registration in the [authentication settings](../../authentication/login-registration-settings/). A person can then create their own user from the home page by clicking on the **Sign in** button (top right), then on the **Create a new account** link in the sign in box.
Enter values in all fields (they cannot be left blank). The email field must be a valid email address that is not used in this system. Click the **Create** button. Depending on the [settings](../../authentication/authentication-settings) the account is created but it could be that it still needs to be activated by an administrator.
Enter values in all fields (they cannot be left blank). The email field must be a valid email address that is not used in this system. Click the **Create** button. Depending on the [settings](../../authentication/login-registration-settings/) the account is created but it could be that it still needs to be activated by an administrator.
#### Activate users
+2 -2
View File
@@ -113,7 +113,7 @@ If you want to set a project to public, you can do so by ticking the box next to
Setting a project to public will make it accessible to all people within your OpenProject instance.
(Should your instance be [accessible without authentication](../../system-admin-guide/authentication/authentication-settings) this option will make the project visible to the general public outside your registered users, too)
(Should your instance be [accessible without authentication](../../system-admin-guide/authentication/login-registration-settings/) this option will make the project visible to the general public outside your registered users, too)
### Copy a project
@@ -172,4 +172,4 @@ If you want to delete a project, navigate to the [Project settings](project-sett
You can also delete a project via the [projects overview list](./project-lists/).
> [!NOTE]
> Deleting projects is only available for System administrators.
> Deleting projects is only available for System administrators.
@@ -2,7 +2,7 @@
$search-input-height-mobile: 36px
@media screen and (max-width: $breakpoint-sm)
@media screen and (max-width: $breakpoint-md)
.top-menu-search
&--input
display: none
@@ -32,6 +32,9 @@ $search-input-height-mobile: 36px
.ng-input input
height: 36px
.ng-dropdown-panel
width: 100% !important
.top-menu-search--back-button
@include unset-button-styles
display: initial
@@ -17,8 +17,9 @@
[accesskey]="4"
class="global-search"
[placeholder]="placeholder"
[dropdownPosition]="'bottom'"
[classes]="{'top-menu-search--input': true, '-markable': markable, '-expanded': expanded }"
[inputAttrs]="{ 'class': 'global-search--input', 'name': 'global-search--input' }"
[inputAttrs]="{ 'class': 'global-search--input', 'name': 'global-search--input', autocomplete: 'off'}"
[openOnEnter]="true"
[labelRequired]="false"
[focusDirectly]="isFocusedDirectly"
@@ -44,8 +45,8 @@
<ng-template op-autocompleter-option-tmp let-item let-index="index" let-search="searchTerm">
<div *ngIf="!item.id; else workPackageItemTemplate">
<div tabindex="-1" class="global-search--option" (click)="followItem(item)">
<div class="global-search--search-term"> {{currentValue}}</div>
<div class="global-search--project-scope" title="{{item.projectScope}}">{{item.text}} ↵</div>
<div class="global-search--search-term">{{ currentValue }}</div>
<div class="global-search--project-scope" title="{{ item.projectScope }}">{{ item.text }} ↵</div>
</div>
</div>
<ng-template #workPackageItemTemplate>
@@ -56,30 +57,28 @@
(click)="redirectToWp(item.id, $event)"
style="line-height: 1"
>
<op-principal
[hideName]="true"
[principal]="item.author"
[hoverCard]="true"
class="hidden-for-mobile global-search--principal"
></op-principal>
<div class="global-search--option-meta">
<span class="global-search--wp-subject">
<div class="global-search--wp-line">
<span [ngClass]="highlighting('type', item.type.id)">
{{ item.type.name }}
</span>
<span>#{{ item.id }}</span>
<span [ngClass]="statusHighlighting(item.status.id)">
{{ item.status.name }}
</span>
<span>-</span>
<span
[ngClass]="highlighting('type', item.type.id)"
[textContent]="item.type.name"
></span>
{{item.subject}}
</span>
<div class="global-search--wp-content">
<span [textContent]="item.project.name" class="global-search--wp-project"></span>
#{{item.id}}
<span
[textContent]="item.status.name"
[ngClass]="statusHighlighting(item.status.id)"
class="global-search--wp-status"
></span>
class="global-search--wp-project"
[attr.title]="item.project.name"
>
{{ item.project.name }}
</span>
</div>
<span
class="global-search--wp-subject"
>
{{ item.subject }}
</span>
</div>
</a>
</ng-template>
@@ -108,6 +108,9 @@ $search-input-height: 30px
max-height: 80vh
height: auto
.ng-dropdown-panel
width: 500px !important
.ng-option
border-bottom: 1px solid var(--borderColor-default)
white-space: normal
@@ -357,7 +357,7 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
return this
.apiV3Service
.work_packages
.filterByTypeaheadOrId(query, idOnly);
.filterByTypeaheadOrId(query, idOnly, { pageSize: '20' });
}
private searchResultsToOptions(results:WorkPackageResource[], query:string) {
@@ -33,7 +33,7 @@
flex-grow: 0
flex-shrink: 1
border: 1px solid var(--button-default-borderColor-rest)
font-size: 13px
font-size: var(--text-body-size-small)
background: var(--button--background-color)
border-radius: 2px
padding: 0 4px
@@ -45,33 +45,40 @@
margin-left: -5px
&--wp-content
display: grid
grid-template-columns: 50% 1fr auto
grid-template-areas: "project idlink status"
display: flex
flex-direction: column
font-size: 0.8rem
padding: 5px 0 5px 0px
padding: 5px 0
&--wp-line
display: flex
flex-wrap: nowrap
gap: var(--base-size-8)
font-size: var(--text-body-size-small)
white-space: nowrap
overflow: hidden
margin-bottom: var(--base-size-4)
&--wp-project
grid-area: project
flex-shrink: 1
min-width: 0
max-width: 100%
@include text-shortener
&--wp-id
grid-area: idlink
place-self: right
font-style: italic
color: var(--fgColor-muted)
font-size: 13px
font-size: var(--text-body-size-small)
white-space: nowrap
&--wp-status
grid-area: status
overflow: hidden
font-style: italic
&--wp-subject
font-weight: var(--base-text-weight-bold)
display: inline-block
overflow: hidden
white-space: nowrap
text-overflow: ellipsis
white-space: normal
overflow-wrap: break-word
line-height: 1.5
@@ -59,8 +59,8 @@ module My
action: "my--time-tracking#newTimeEntry",
"my--time-tracking-date-param" => options[:date]
},
label: t("button_log_time"),
aria: { label: t("button_log_time") }
label: t(:button_add_time_entry),
aria: { label: t(:button_add_time_entry) }
))
end
@@ -58,7 +58,13 @@ module Meetings
if recurring?
helpers.format_time(model.start_time, include_date: false)
else
safe_join([helpers.format_date(model.start_time), helpers.format_time(model.start_time, include_date: false)], " ")
safe_join(
[
helpers.format_time_as_date(model.start_time),
helpers.format_time(model.start_time, include_date: false)
],
" "
)
end
end
@@ -50,7 +50,7 @@
details.with_row do
render_meeting_attribute_row(:calendar) do
render(Primer::Beta::Text.new) do
format_date(@meeting.start_time)
format_time_as_date(@meeting.start_time)
end
end
end
@@ -397,7 +397,7 @@ class MeetingsController < ApplicationController
.where(start_time: ...next_week)
.order(start_time: :asc)
.each do |meeting|
start_date = meeting.start_time.to_date
start_date = in_user_zone(meeting.start_time).to_date
group_key =
if start_date == Time.zone.today
@@ -54,7 +54,7 @@ class WorkPackageMeetingsTabController < ApplicationController
end
def count
count = Meeting.visible.where(id: @work_package.meetings.select(:id)).count
count = Meeting.visible.not_templated.where(id: @work_package.meetings.select(:id)).count
render json: { count: }
end
@@ -30,7 +30,9 @@
require "rails_helper"
RSpec.describe Meetings::SidePanel::DetailsComponent, type: :component do
RSpec.describe Meetings::SidePanel::DetailsComponent,
type: :component,
with_settings: { date_format: "%Y-%m-%d" } do
let(:user) { build_stubbed(:user) }
subject do
@@ -73,4 +75,28 @@ RSpec.describe Meetings::SidePanel::DetailsComponent, type: :component do
expect(subject).to have_text("Wednesday")
end
end
context "with a meeting date on the day border" do
let(:meeting) do
build_stubbed(:meeting, start_time: DateTime.parse("2025-05-09T22:00:00Z"))
end
context "with a user in a +3 time zone" do
let(:user) { build_stubbed(:user, preferences: { time_zone: "Africa/Nairobi" }) }
it "formats the date for the current user" do
expect(subject).to have_text("2025-05-10")
expect(subject).to have_text("01:00 AM - 02:00 AM")
end
end
context "with a user in a UTC time zone" do
let(:user) { build_stubbed(:user, preferences: { time_zone: "Etc/UTC" }) }
it "formats the date for the current user" do
expect(subject).to have_text("2025-05-09")
expect(subject).to have_text("10:00 PM - 11:00 PM")
end
end
end
end
@@ -54,6 +54,15 @@ RSpec.describe "Meeting index",
author: user)
end
shared_let(:tonight) do
create(:meeting,
:author_participates,
title: "meeting starting tonight",
start_time: DateTime.parse("2025-01-29T22:00:00Z"),
project:,
author: user)
end
shared_let(:tomorrow) do
create(:meeting,
:author_participates,
@@ -126,6 +135,7 @@ RSpec.describe "Meeting index",
today = page.find("[data-test-selector='meetings-table-today']")
expect(today).to have_text "meeting starting soon"
expect(today).to have_text "meeting starting tonight"
tomorrow = page.find("[data-test-selector='meetings-table-tomorrow']")
expect(tomorrow).to have_text "meeting starting tomorrow"
@@ -139,6 +149,31 @@ RSpec.describe "Meeting index",
expect(later).to have_text "meeting on next friday"
end
context "when we request as a user with different time zone" do
before do
user.pref.time_zone = "Asia/Tokyo"
user.save!
end
it "shows the meetings in the user's time zone" do
expect(subject).to have_http_status(:ok)
content = page.find_by_id("content")
expect(content).to have_text "Tomorrow"
expect(content).to have_text "Later this week"
expect(content).to have_text "Next week and later"
expect(content).to have_no_text "an earlier meeting"
today = page.find("[data-test-selector='meetings-table-today']")
expect(today).to have_text "meeting starting soon"
expect(today).to have_no_text "meeting starting tonight"
tomorrow = page.find("[data-test-selector='meetings-table-tomorrow']")
expect(tomorrow).to have_text "meeting starting tomorrow"
expect(tomorrow).to have_text "meeting starting tonight"
end
end
context "when we request after 10am" do
let(:current_time) { "2025-01-29T14:00:00Z".to_datetime }
@@ -158,6 +193,7 @@ RSpec.describe "Meeting index",
context "when some meeting groups are empty" do
before do
today.destroy!
tonight.destroy!
end
it "shows only the matching bucket" do
@@ -83,7 +83,7 @@ RSpec.describe WorkPackages::BulkController, with_settings: { journal_aggregatio
principal: user,
roles: [role])
end
shared_let(:work_package1) do
shared_let(:work_package1, refind: true) do
create(:work_package,
author: user,
assigned_to: user,
@@ -93,7 +93,7 @@ RSpec.describe WorkPackages::BulkController, with_settings: { journal_aggregatio
custom_field_values: { custom_field1.id => custom_field_value },
project: project1)
end
shared_let(:work_package2) do
shared_let(:work_package2, refind: true) do
create(:work_package,
author: user,
assigned_to: user,
@@ -103,7 +103,7 @@ RSpec.describe WorkPackages::BulkController, with_settings: { journal_aggregatio
custom_field_values: { custom_field1.id => custom_field_value },
project: project1)
end
shared_let(:work_package3) do
shared_let(:work_package3, refind: true) do
create(:work_package,
author: user,
type:,
@@ -645,5 +645,23 @@ RSpec.describe WorkPackages::BulkController, with_settings: { journal_aggregatio
expect(response).to redirect_to(project_work_packages_path(work_package1.project))
end
end
context "with children work packages following each other" do
before_all do
work_package1.update(subject: "parent", schedule_manually: false)
work_package2.update(subject: "predecessor child", parent: work_package1, schedule_manually: true)
work_package3.update(subject: "successor child", parent: work_package1, schedule_manually: false)
create(:follows_relation, predecessor: work_package2, successor: work_package3)
end
let(:params) { { "ids" => [work_package1.id, work_package2.id, work_package3.id] } }
it "deletes them all without errors" do
expect { send_destroy_request }.not_to raise_error
expect(WorkPackage.count).to eq(0)
expect(response).to redirect_to(project_work_packages_path(project1))
end
end
end
end
+10 -10
View File
@@ -36,7 +36,7 @@ RSpec.describe "Search", :js, :selenium, with_settings: { per_page_options: "5"
shared_let(:admin) { create(:admin) }
shared_let(:project) { create(:project) }
shared_let(:work_packages) do
(1..12).map do |n|
(1..22).map do |n|
Timecop.freeze("2016-11-21 #{n}:00".to_datetime) do
subject = "Subject No. #{n} WP"
create(:work_package,
@@ -110,7 +110,7 @@ RSpec.describe "Search", :js, :selenium, with_settings: { per_page_options: "5"
global_search.search(query, submit: false)
# Suggestions shall show latest WPs first.
global_search.expect_work_package_option(work_packages[11])
global_search.expect_work_package_option(work_packages[21])
# and show maximum 10 suggestions.
global_search.expect_work_package_option(work_packages[2])
global_search.expect_no_work_package_option(work_packages[1])
@@ -313,8 +313,8 @@ RSpec.describe "Search", :js, :selenium, with_settings: { per_page_options: "5"
table = Pages::EmbeddedWorkPackagesTable.new(find(".work-packages-embedded-view--container"))
table.expect_work_package_count(5) # because we set the page size to this
# Expect order to be from newest to oldest.
table.expect_work_package_listed(*work_packages[7..12]) # This line ensures that the table is completely rendered.
table.expect_work_package_order(*work_packages[7..12].map { |wp| wp.id.to_s }.reverse)
table.expect_work_package_listed(*work_packages[17..22]) # This line ensures that the table is completely rendered.
table.expect_work_package_order(*work_packages[17..22].map { |wp| wp.id.to_s }.reverse)
# Expect that "Advanced filters" can refine the search:
filters.expect_closed
@@ -567,14 +567,14 @@ RSpec.describe "Search", :js, :selenium, with_settings: { per_page_options: "5"
describe "pagination" do
context "for project wide search" do
it "works" do
expect_range 3, 12
expect_range 13, 22
click_on "Next", match: :first
expect_range 1, 2
expect_range 11, 12
expect(page).to have_current_path /\/projects\/#{project.identifier}\/search/
click_on "Previous", match: :first
expect_range 3, 12
expect_range 13, 22
expect(page).to have_current_path /\/projects\/#{project.identifier}\/search/
end
end
@@ -587,13 +587,13 @@ RSpec.describe "Search", :js, :selenium, with_settings: { per_page_options: "5"
end
it "works" do
expect_range 3, 12
expect_range 13, 22
click_on "Next", match: :first
expect_range 1, 2
expect_range 11, 12
click_on "Previous", match: :first
expect_range 3, 12
expect_range 13, 22
end
end
end
@@ -135,10 +135,14 @@ RSpec.describe WorkPackages::DeleteService do
context "with descendants" do
let(:child) do
build_stubbed(:work_package)
build_stubbed(:work_package).tap do |wp|
allow(wp).to receive(:reload).and_return(wp)
end
end
let(:grandchild) do
build_stubbed(:work_package)
build_stubbed(:work_package).tap do |wp|
allow(wp).to receive(:reload).and_return(wp)
end
end
let(:descendants) do
[child, grandchild]