Skip to content

Commit fccda44

Browse files
committed
Merge branch 'release/14.0' into dev
2 parents 74331ac + ed6c039 commit fccda44

File tree

18 files changed

+247
-34
lines changed

18 files changed

+247
-34
lines changed

app/models/projects/acts_as_customizable_patches.rb

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def with_all_available_custom_fields
108108
# in order to support implicit activation of custom fields when values are provided during an update
109109
self._query_available_custom_fields_on_global_level = true
110110
result = yield
111-
self._query_available_custom_fields_on_global_level = false
111+
self._query_available_custom_fields_on_global_level = nil
112112

113113
result
114114
end
@@ -118,10 +118,21 @@ def available_custom_fields(global: false)
118118
# in contrast to acts_as_customizable, custom_fields are enabled per project
119119
# thus we need to check the project_custom_field_project_mappings
120120
custom_fields = ProjectCustomField
121-
.visible
122121
.includes(:project_custom_field_section)
123122
.order("custom_field_sections.position", :position_in_custom_field_section)
124123

124+
# Do not hide the invisble fields when accessing via the _query_available_custom_fields_on_global_level
125+
# flag. Due to the internal working of the acts_as_customizable plugin, when a project admin updates
126+
# the custom fields, it will clear out all the hidden fields that are not visible for them.
127+
# This happens because the `#ensure_custom_values_complete` will gather all the `custom_field_values`
128+
# and assigns them to the custom_fields association. If the `custom_field_values` do not contain the
129+
# hidden fields, they will be cleared from the association. The `custom_field_values` will contain the
130+
# hidden fields, only if they are returned from this method. Hence we should not hide them,
131+
# when accessed with the _query_available_custom_fields_on_global_level flag on.
132+
unless _query_available_custom_fields_on_global_level
133+
custom_fields = custom_fields.visible
134+
end
135+
125136
# available_custom_fields is called from within the acts_as_customizable module
126137
# we don't want to adjust these calls, but need a way to query the available custom fields on a global level in some cases
127138
# thus we pass in this parameter as an instance flag implicitly here,
@@ -155,8 +166,11 @@ def custom_field_values=(values)
155166
with_all_available_custom_fields { super }
156167
end
157168

158-
# we need to query the available custom fields on a global level when
159-
# trying to set a custom field which is not enabled via e.g. custom_field_123="foo"
169+
# We need to query the available custom fields on a global level when
170+
# trying to set a custom field which is not enabled via the API e.g. custom_field_123="foo"
171+
# This implies implicit activation of the disabled custom fields via the API. As a side effect,
172+
# we will have empty CustomValue objects created for each custom field, regardless of its
173+
# enabled/disabled state in the project.
160174
def for_custom_field_accessor(method_symbol)
161175
with_all_available_custom_fields { super }
162176
end

app/services/work_packages/set_attributes_service.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,14 @@ def update_done_ratio
332332
end
333333

334334
def round_progress_values
335-
work_package.estimated_hours = work_package.estimated_hours&.round(2)
336-
work_package.remaining_hours = work_package.remaining_hours&.round(2)
335+
rounded = work_package.estimated_hours&.round(2)
336+
if rounded != work_package.estimated_hours
337+
work_package.estimated_hours = rounded
338+
end
339+
rounded = work_package.remaining_hours&.round(2)
340+
if rounded != work_package.remaining_hours
341+
work_package.remaining_hours = rounded
342+
end
337343
end
338344

339345
def update_remaining_hours_from_percent_complete

docs/system-admin-guide/file-storages/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,8 @@ For detailed guide on the initial setup, please consult [OneDrive/SharePoint int
3131
Please also remember to activate the **File storages** module under [project settings in a respective project](../../user-guide/projects/project-settings/file-storages/).
3232

3333
For instructions on using the integration after the setup has been complete please refer to [SharePoint/OneDrive integration user guide](../../user-guide/file-management/one-drive-integration/).
34+
35+
## File storage troubleshooting
36+
37+
For troubleshooting guidance related to file storages, visit the [File storage troubleshooting](./file-storage-troubleshooting) page. Here you will find possible explanations and suggested solutions. If you encounter any challenges not addressed here, do not hesitate to reach out to the [OpenProject community](https://community.openproject.org/projects/openproject/forums) or [support team](https://www.openproject.org/contact/) for further assistance.
38+
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
sidebar_navigation:
3+
title: File storages troubleshooting
4+
priority: 999
5+
description: File storages troubleshooting in OpenProject.
6+
keywords: file storages, Nextcloud error, Nextcloud troubleshooting, OneDrive error, Sharepoint error, OneDrive, Sharepoint, error, troubleshooting
7+
---
8+
9+
# File storage errors and troubleshooting
10+
11+
## Unhealthy file storages email notifications
12+
13+
In some cases it is possible that a file storage has been set-up incorrectly, the connection is faulty or the storage itself has problems. In this case the respective message will appear under *Administration* -> *File storages*.
14+
15+
> Please note that this only applies to file storages where **automatically managed project folders** have been selected.
16+
17+
![Health check for automatically managed folders in file storage integrations in OpenProject](openproject_file_storages_health_message.png)
18+
19+
If a problem has been detected for a file storage with automatically managed folders enabled, the OpenProject adminstrators will be notified via email of the detected error. Admin will be notified once a day of the faulty integration, including the specific error description and solution suggestions (see the section below).
20+
21+
Once the error has been resolved, the admin will also receive an email informing him/her/them of this.
22+
23+
You can choose to subscribe or unsubscribe to these email notifications by clicking the respective button under the error message.
24+
25+
## File storage errors description
26+
27+
Once you have set up your files storages, it is possible that a technical error may occur. Please consult the following table for possible reasons behind the errors and suggested solutions.
28+
29+
30+
31+
| Error name | Error description | Possible reasons | Next steps and solutions |
32+
| ------------ | --------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
33+
| Error | No group specified | The name may not be specified for the storage.<br/>A glitch during setup or manual changes to the DB could cause this problem. The group name is saved in the database in the Storages Table in the providers field (JSON). | Setup the entire storage again. |
34+
| Error | Group does not exist | The app was activated on Nextcloud and the OpenProject group was removed afterwards.<br/>Changes on Nextlcoud: OpenProject group was removed. | Manually add the group in the Nextcloud setup and call it OpenProject. Add the user OpenProject to the group OpenProject. |
35+
| Error | User does not exist | After the app was activated on Nextcloud and the user was removed afterwards.<br /> Changes on Nextlcoud: OpenProject user was removed. | Manually add the user in the Nextcloud setup and call that user OpenProject. Add the user OpenProject to the group OpenProject. <br />Alternatively reinstall the OpenProject integration app on Nextcloud. You will also need to reconfigure the Nextcloud storage. |
36+
| Error | Insufficient privileges | OpenProject can not change the user permissions for folders or add folders to the OpenProject folder, because the OpenProject user no longer has access to the folder. | Reinstall the OpenProject integration app on Nextcloud. You will need to reconfigure the Nextcloud storage. Make sure the OpenProject user is the admin of the OpenProject group and also the admin of the OpenProject folder. |
37+
| Error | Failed to remove or add user from group | A user does not exist in the file storage. <br />A user can not be removed from the OpenProject group due to admin rights. <br />This may occur when running the sync job and further information can be found in the server logs. | Ensure that the user exists in the file storage platform. <br />Remove admin rights for that user on the OpenProject group. <br />If the user is also an admin in the files storage group, he/she/they need to be removed by a file storage platform admin. |
38+
| Not allowed | Outbound request method not allowed | OpenProject sent wrong requests to the storage. <br />This error can occur both in Nextcloud and OneDrive/Sharepoint integration. | Report this to [OpenProject community](https://community.openproject.org/projects/openproject/forums) or [support team](https://www.openproject.org/contact/). |
39+
| Not found | Outbound request destination not found | OpenProject can not reach file storage platform. <br />This could be due to Storage provider being down:<br />- DNS problems <br />- Network problems (flaky network) <br />- Local networks (Nexctloud specific setting that needs to enabled) | See if you can access the file storage platform from your browser. <br />For Nextcloud, see if Nextcloud settings are active if in local network. |
40+
| Unauthorized | Outbound request not authorized | - Authentification is failing<br /> - Application password was changed and not updated in OpenProject (Nextcloud OAuth settings are wrong or OneDrive/SharePoint client secret or ID is wrong).<br />- User has no access, can not login, no token can be negotiated.<br /> Server to server: the client secret might be wrong <br /> OpenProject User credentials might be wrong | Check the storages setup.<br />Check if the client secret (OneDrive/SharePoint) or the OAuth setup is correct (Nextcloud).<br />Check if the application password is correct. |
41+
| Conflict | *error_text_from_response* | A folder or a file was created, which already exists on the storage platform, e.g. a folder with the same name exists. <br /> Can happen if for example a user manually created something on the storage platform. | Check in the storage platform if the folder already exists. |
42+
| Error | Outbound request failed | An unexpected 500 error, e.g. TOS (Terms of Service) app was activated and OpenProject can not access storage anymore. <br /> Password configuration plugin may have caused problems. | See if file storage is working correctly. If it does, collect as much information as possible and contact [OpenProject community](https://community.openproject.org/projects/openproject/forums) or [support team](https://www.openproject.org/contact/). |
43+
44+
45+
46+
47+
If the suggested troubleshooting solutions did not resolve your issue, please reach out to the [OpenProject community](https://community.openproject.org/projects/openproject/forums) or [support team](https://www.openproject.org/contact/) for further assistance.

docs/system-admin-guide/users-permissions/roles-permissions/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,28 @@ A user is any individual who can log into your OpenProject instance.
1515

1616
Permissions control what users can see and do within OpenProject. Permission are granted to users by assigning one or more roles to the users.
1717

18+
### File storages permissions
19+
20+
File storages permissions include the following:
21+
22+
![Files storages permissions in OpenProject](openproject_user_guide_file_storages_permissions.png)
23+
24+
Following are the permissions for file storages within OpenProject:
25+
26+
- **View file links**: Allows a user to see file links to external storages Files tab of work packages
27+
- **Manage file links**: Allows a user to create and edit file links to work packages
28+
- **Manage file storages in project**: Allows a user to add or edit file storages for a project
29+
30+
Following user permissions are set on files and folder in **External Storages**:
31+
32+
- **External Storage: Read files (Nextcloud, OneDrive/SharePoint)**
33+
- **External Storage: Write files (Nextcloud, OneDrive/SharePoint)**
34+
- **External Storage: Create files (Nextcloud)**
35+
- **External Storage: Share files (Nextcloud)**
36+
- **External Storage: Delete files (Nextcloud)**
37+
38+
> Please note that not all file permissions are applicable to all storage providers.
39+
1840
## Roles
1941

2042
A role bundles a collection of permissions. It is an convenient way of granting permissions to multiple users in your organization that need the same permissions or restrictions.

frontend/src/app/features/hal/resources/query-resource.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { QueryOrder } from 'core-app/core/apiv3/endpoints/queries/apiv3-query-or
3333
import { WorkPackageCollectionResource } from 'core-app/features/hal/resources/wp-collection-resource';
3434
import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/query-filter-instance-resource';
3535
import { ProjectResource } from 'core-app/features/hal/resources/project-resource';
36+
import { UserResource } from 'core-app/features/hal/resources/user-resource';
3637
import { QuerySortByResource } from 'core-app/features/hal/resources/query-sort-by-resource';
3738
import { QueryGroupByResource } from 'core-app/features/hal/resources/query-group-by-resource';
3839

@@ -41,6 +42,7 @@ export interface QueryResourceEmbedded {
4142
columns:QueryColumn[];
4243
groupBy:QueryGroupByResource|undefined;
4344
project:ProjectResource;
45+
user:UserResource;
4446
sortBy:QuerySortByResource[];
4547
filters:QueryFilterInstanceResource[];
4648
}
@@ -96,6 +98,8 @@ export class QueryResource extends HalResource {
9698

9799
public hidden:boolean;
98100

101+
public user:UserResource;
102+
99103
public project:ProjectResource;
100104

101105
public includeSubprojects:boolean;

frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ import { States } from 'core-app/core/states/states.service';
3131
import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service';
3232
import { StateService } from '@uirouter/core';
3333
import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space';
34-
import { Injectable } from '@angular/core';
34+
import { Injectable, Injector } from '@angular/core';
35+
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
3536
import isPersistedResource from 'core-app/features/hal/helpers/is-persisted-resource';
3637
import { UrlParamsHelperService } from 'core-app/features/work-packages/components/wp-query/url-params-helper';
3738
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
@@ -43,6 +44,7 @@ import {
4344
WorkPackageViewPaginationService,
4445
} from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-pagination.service';
4546
import { ConfigurationService } from 'core-app/core/config/configuration.service';
47+
import { CurrentUserService } from 'core-app/core/current-user/current-user.service';
4648
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
4749
import { ApiV3QueriesPaths } from 'core-app/core/apiv3/endpoints/queries/apiv3-queries-paths';
4850
import { ApiV3QueryPaths } from 'core-app/core/apiv3/endpoints/queries/apiv3-query-paths';
@@ -61,6 +63,8 @@ export interface QueryDefinition {
6163

6264
@Injectable()
6365
export class WorkPackagesListService {
66+
@InjectField() protected readonly currentUser:CurrentUserService;
67+
6468
// We remember the query requests coming in so we can ensure only the latest request is being tended to
6569
private queryRequests = input<QueryDefinition>();
6670

@@ -73,7 +77,7 @@ export class WorkPackagesListService {
7377
// Map the observable from the stream to a new one that completes when states are loaded
7478
mergeMap((query:QueryResource) => {
7579
// load the form if needed
76-
this.conditionallyLoadForm(query);
80+
void this.conditionallyLoadForm(query);
7781

7882
// Project the loaded query into the table states and confirm the query is fully loaded
7983
this.wpStatesInitialization.initialize(query, query.results);
@@ -85,6 +89,7 @@ export class WorkPackagesListService {
8589
);
8690

8791
constructor(
92+
readonly injector:Injector,
8893
protected toastService:ToastService,
8994
readonly I18n:I18nService,
9095
protected UrlParamsHelper:UrlParamsHelperService,
@@ -282,21 +287,7 @@ export class WorkPackagesListService {
282287
.then(() => {
283288
this.toastService.addSuccess(this.I18n.t('js.notice_successful_delete'));
284289

285-
if (this.$state.$current.data.hardReloadOnBaseRoute) {
286-
const url = new URL(window.location.href);
287-
url.search = '';
288-
window.location.href = url.href;
289-
} else {
290-
let projectId;
291-
if (query.project) {
292-
projectId = query.project.href!.split('/').pop();
293-
}
294-
295-
void this.loadDefaultQuery(projectId);
296-
297-
this.states.changes.queries.next(query.id!);
298-
this.reloadSidemenu(null);
299-
}
290+
void this.navigateToDefaultQuery(query);
300291
});
301292

302293
return promise;
@@ -317,10 +308,14 @@ export class WorkPackagesListService {
317308
void promise
318309
.then(() => {
319310
this.toastService.addSuccess(this.I18n.t('js.notice_successful_update'));
320-
321-
this.$state.go('.', { query_id: query!.id, query_props: null }, { reload: true });
322-
this.states.changes.queries.next(query!.id!);
323-
this.reloadSidemenu(query.id);
311+
const queryAccessibleByUser = query.public || query.user.id === this.currentUser.userId;
312+
if (queryAccessibleByUser) {
313+
void this.$state.go('.', { query_id: query.id, query_props: null }, { reload: true });
314+
this.states.changes.queries.next(query.id);
315+
this.reloadSidemenu(query.id);
316+
} else {
317+
this.navigateToDefaultQuery(query);
318+
}
324319
})
325320
.catch((error:ErrorResource) => {
326321
this.toastService.addError(error.message);
@@ -435,6 +430,26 @@ export class WorkPackagesListService {
435430
);
436431
}
437432

433+
private navigateToDefaultQuery(query:QueryResource):void {
434+
const { hardReloadOnBaseRoute } = this.$state.$current.data as { hardReloadOnBaseRoute?:boolean };
435+
436+
if (hardReloadOnBaseRoute) {
437+
const url = new URL(window.location.href);
438+
url.search = '';
439+
window.location.href = url.href;
440+
} else {
441+
let projectId;
442+
if (query.project.href) {
443+
projectId = query.project.href.split('/').pop();
444+
}
445+
446+
void this.loadDefaultQuery(projectId);
447+
448+
this.states.changes.queries.next(query.id);
449+
this.reloadSidemenu(null);
450+
}
451+
}
452+
438453
private reloadSidemenu(selectedQueryId:string|null):void {
439454
const menuIdentifier:string|undefined = this.$state.current.data.sidemenuId;
440455

frontend/src/app/shared/components/fields/edit/field-types/progress-popover-edit-field.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<spot-drop-modal
22
[opened]="opened"
33
(closed)="onModalClosed()"
4-
alignment="bottom-center"
4+
alignment="bottom-start"
55
>
66
<input
77
slot="trigger"

frontend/src/app/spot/components/drop-modal/drop-modal.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ export class SpotDropModalComponent implements OnDestroy {
4040
@Input() public allowRepositioning = true;
4141

4242
/**
43-
* The default alignment of the drop modal. There are twelve alignments in total. You can check which ones they are
44-
* from the `SpotDropAlignmentOption` Enum that is available in 'core-app/spot/drop-alignment-options'.
43+
* The default alignment of the drop modal. There are twelve alignments in total. You can check which ones they are in
44+
* @floating-ui/utils: `Placement` in floating-ui.utils.d.ts
4545
*/
4646
@Input() public alignment:Placement = 'bottom-start';
4747

frontend/src/app/spot/styles/sass/components/drop-modal.sass

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
position: absolute
88
@include spot-z-index("drop-modal", 1)
9-
pointer-events: none
109

1110
pointer-events: all
1211
opacity: 1

modules/backlogs/config/locales/crowdin/id.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ id:
150150
remaining_hours: "pekerjaan yang tersisa"
151151
required_burn_rate_hours: "tingkat pembakaran yang dibutuhkan (jam)"
152152
required_burn_rate_points: "tingkat pembakaran yang dibutuhkan (poin)"
153-
todo_work_package_description: "%{total}: %{url}\n%{keterangan}"
153+
todo_work_package_description: "%{summary}: %{url}\n%{description}"
154154
todo_work_package_summary: "%{type}: %{summary}"
155155
version_settings_display_label: "Kolom di backlog"
156156
version_settings_display_option_left: "kiri"

0 commit comments

Comments
 (0)