Merge branch 'release/16.0' into dev
@@ -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.
|
||||
|
||||

|
||||
|
||||
## 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**.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 402 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 14 KiB |
@@ -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 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 655 KiB |
|
After Width: | Height: | Size: 551 KiB |
|
After Width: | Height: | Size: 856 KiB |
|
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.
|
||||
|
||||

|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 104 KiB |
@@ -55,7 +55,7 @@ If you are using [Enterprise cloud](../../../enterprise-guide/enterprise-cloud-g
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||