Skip to content

Commit

Permalink
Merge branch 'release/14.0' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
openprojectci committed Apr 20, 2024
2 parents 74331ac + ed6c039 commit fccda44
Show file tree
Hide file tree
Showing 18 changed files with 247 additions and 34 deletions.
22 changes: 18 additions & 4 deletions app/models/projects/acts_as_customizable_patches.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def with_all_available_custom_fields
# in order to support implicit activation of custom fields when values are provided during an update
self._query_available_custom_fields_on_global_level = true
result = yield
self._query_available_custom_fields_on_global_level = false
self._query_available_custom_fields_on_global_level = nil

result
end
Expand All @@ -118,10 +118,21 @@ def available_custom_fields(global: false)
# in contrast to acts_as_customizable, custom_fields are enabled per project
# thus we need to check the project_custom_field_project_mappings
custom_fields = ProjectCustomField
.visible
.includes(:project_custom_field_section)
.order("custom_field_sections.position", :position_in_custom_field_section)

# Do not hide the invisble fields when accessing via the _query_available_custom_fields_on_global_level
# flag. Due to the internal working of the acts_as_customizable plugin, when a project admin updates
# the custom fields, it will clear out all the hidden fields that are not visible for them.
# This happens because the `#ensure_custom_values_complete` will gather all the `custom_field_values`
# and assigns them to the custom_fields association. If the `custom_field_values` do not contain the
# hidden fields, they will be cleared from the association. The `custom_field_values` will contain the
# hidden fields, only if they are returned from this method. Hence we should not hide them,
# when accessed with the _query_available_custom_fields_on_global_level flag on.
unless _query_available_custom_fields_on_global_level
custom_fields = custom_fields.visible
end

# available_custom_fields is called from within the acts_as_customizable module
# 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
# thus we pass in this parameter as an instance flag implicitly here,
Expand Down Expand Up @@ -155,8 +166,11 @@ def custom_field_values=(values)
with_all_available_custom_fields { super }
end

# we need to query the available custom fields on a global level when
# trying to set a custom field which is not enabled via e.g. custom_field_123="foo"
# We need to query the available custom fields on a global level when
# trying to set a custom field which is not enabled via the API e.g. custom_field_123="foo"
# This implies implicit activation of the disabled custom fields via the API. As a side effect,
# we will have empty CustomValue objects created for each custom field, regardless of its
# enabled/disabled state in the project.
def for_custom_field_accessor(method_symbol)
with_all_available_custom_fields { super }
end
Expand Down
10 changes: 8 additions & 2 deletions app/services/work_packages/set_attributes_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,14 @@ def update_done_ratio
end

def round_progress_values
work_package.estimated_hours = work_package.estimated_hours&.round(2)
work_package.remaining_hours = work_package.remaining_hours&.round(2)
rounded = work_package.estimated_hours&.round(2)
if rounded != work_package.estimated_hours
work_package.estimated_hours = rounded
end
rounded = work_package.remaining_hours&.round(2)
if rounded != work_package.remaining_hours
work_package.remaining_hours = rounded
end
end

def update_remaining_hours_from_percent_complete
Expand Down
5 changes: 5 additions & 0 deletions docs/system-admin-guide/file-storages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ For detailed guide on the initial setup, please consult [OneDrive/SharePoint int
Please also remember to activate the **File storages** module under [project settings in a respective project](../../user-guide/projects/project-settings/file-storages/).

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

## File storage troubleshooting

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.

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
sidebar_navigation:
title: File storages troubleshooting
priority: 999
description: File storages troubleshooting in OpenProject.
keywords: file storages, Nextcloud error, Nextcloud troubleshooting, OneDrive error, Sharepoint error, OneDrive, Sharepoint, error, troubleshooting
---

# File storage errors and troubleshooting

## Unhealthy file storages email notifications

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

> Please note that this only applies to file storages where **automatically managed project folders** have been selected.
![Health check for automatically managed folders in file storage integrations in OpenProject](openproject_file_storages_health_message.png)

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

Once the error has been resolved, the admin will also receive an email informing him/her/them of this.

You can choose to subscribe or unsubscribe to these email notifications by clicking the respective button under the error message.

## File storage errors description

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.



| Error name | Error description | Possible reasons | Next steps and solutions |
| ------------ | --------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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/). |
| 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. |
| 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. |
| 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. |
| 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/). |




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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,28 @@ A user is any individual who can log into your OpenProject instance.

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

### File storages permissions

File storages permissions include the following:

![Files storages permissions in OpenProject](openproject_user_guide_file_storages_permissions.png)

Following are the permissions for file storages within OpenProject:

- **View file links**: Allows a user to see file links to external storages Files tab of work packages
- **Manage file links**: Allows a user to create and edit file links to work packages
- **Manage file storages in project**: Allows a user to add or edit file storages for a project

Following user permissions are set on files and folder in **External Storages**:

- **External Storage: Read files (Nextcloud, OneDrive/SharePoint)**
- **External Storage: Write files (Nextcloud, OneDrive/SharePoint)**
- **External Storage: Create files (Nextcloud)**
- **External Storage: Share files (Nextcloud)**
- **External Storage: Delete files (Nextcloud)**

> Please note that not all file permissions are applicable to all storage providers.
## Roles

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.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions frontend/src/app/features/hal/resources/query-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { QueryOrder } from 'core-app/core/apiv3/endpoints/queries/apiv3-query-or
import { WorkPackageCollectionResource } from 'core-app/features/hal/resources/wp-collection-resource';
import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/query-filter-instance-resource';
import { ProjectResource } from 'core-app/features/hal/resources/project-resource';
import { UserResource } from 'core-app/features/hal/resources/user-resource';
import { QuerySortByResource } from 'core-app/features/hal/resources/query-sort-by-resource';
import { QueryGroupByResource } from 'core-app/features/hal/resources/query-group-by-resource';

Expand All @@ -41,6 +42,7 @@ export interface QueryResourceEmbedded {
columns:QueryColumn[];
groupBy:QueryGroupByResource|undefined;
project:ProjectResource;
user:UserResource;
sortBy:QuerySortByResource[];
filters:QueryFilterInstanceResource[];
}
Expand Down Expand Up @@ -96,6 +98,8 @@ export class QueryResource extends HalResource {

public hidden:boolean;

public user:UserResource;

public project:ProjectResource;

public includeSubprojects:boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import { States } from 'core-app/core/states/states.service';
import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service';
import { StateService } from '@uirouter/core';
import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space';
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import isPersistedResource from 'core-app/features/hal/helpers/is-persisted-resource';
import { UrlParamsHelperService } from 'core-app/features/work-packages/components/wp-query/url-params-helper';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
Expand All @@ -43,6 +44,7 @@ import {
WorkPackageViewPaginationService,
} from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-pagination.service';
import { ConfigurationService } from 'core-app/core/config/configuration.service';
import { CurrentUserService } from 'core-app/core/current-user/current-user.service';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { ApiV3QueriesPaths } from 'core-app/core/apiv3/endpoints/queries/apiv3-queries-paths';
import { ApiV3QueryPaths } from 'core-app/core/apiv3/endpoints/queries/apiv3-query-paths';
Expand All @@ -61,6 +63,8 @@ export interface QueryDefinition {

@Injectable()
export class WorkPackagesListService {
@InjectField() protected readonly currentUser:CurrentUserService;

// We remember the query requests coming in so we can ensure only the latest request is being tended to
private queryRequests = input<QueryDefinition>();

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

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

constructor(
readonly injector:Injector,
protected toastService:ToastService,
readonly I18n:I18nService,
protected UrlParamsHelper:UrlParamsHelperService,
Expand Down Expand Up @@ -282,21 +287,7 @@ export class WorkPackagesListService {
.then(() => {
this.toastService.addSuccess(this.I18n.t('js.notice_successful_delete'));

if (this.$state.$current.data.hardReloadOnBaseRoute) {
const url = new URL(window.location.href);
url.search = '';
window.location.href = url.href;
} else {
let projectId;
if (query.project) {
projectId = query.project.href!.split('/').pop();
}

void this.loadDefaultQuery(projectId);

this.states.changes.queries.next(query.id!);
this.reloadSidemenu(null);
}
void this.navigateToDefaultQuery(query);
});

return promise;
Expand All @@ -317,10 +308,14 @@ export class WorkPackagesListService {
void promise
.then(() => {
this.toastService.addSuccess(this.I18n.t('js.notice_successful_update'));

this.$state.go('.', { query_id: query!.id, query_props: null }, { reload: true });
this.states.changes.queries.next(query!.id!);
this.reloadSidemenu(query.id);
const queryAccessibleByUser = query.public || query.user.id === this.currentUser.userId;
if (queryAccessibleByUser) {
void this.$state.go('.', { query_id: query.id, query_props: null }, { reload: true });
this.states.changes.queries.next(query.id);
this.reloadSidemenu(query.id);
} else {
this.navigateToDefaultQuery(query);
}
})
.catch((error:ErrorResource) => {
this.toastService.addError(error.message);
Expand Down Expand Up @@ -435,6 +430,26 @@ export class WorkPackagesListService {
);
}

private navigateToDefaultQuery(query:QueryResource):void {
const { hardReloadOnBaseRoute } = this.$state.$current.data as { hardReloadOnBaseRoute?:boolean };

if (hardReloadOnBaseRoute) {
const url = new URL(window.location.href);
url.search = '';
window.location.href = url.href;
} else {
let projectId;
if (query.project.href) {
projectId = query.project.href.split('/').pop();
}

void this.loadDefaultQuery(projectId);

this.states.changes.queries.next(query.id);
this.reloadSidemenu(null);
}
}

private reloadSidemenu(selectedQueryId:string|null):void {
const menuIdentifier:string|undefined = this.$state.current.data.sidemenuId;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<spot-drop-modal
[opened]="opened"
(closed)="onModalClosed()"
alignment="bottom-center"
alignment="bottom-start"
>
<input
slot="trigger"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export class SpotDropModalComponent implements OnDestroy {
@Input() public allowRepositioning = true;

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

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

pointer-events: all
opacity: 1
Expand Down
2 changes: 1 addition & 1 deletion modules/backlogs/config/locales/crowdin/id.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ id:
remaining_hours: "pekerjaan yang tersisa"
required_burn_rate_hours: "tingkat pembakaran yang dibutuhkan (jam)"
required_burn_rate_points: "tingkat pembakaran yang dibutuhkan (poin)"
todo_work_package_description: "%{total}: %{url}\n%{keterangan}"
todo_work_package_description: "%{summary}: %{url}\n%{description}"
todo_work_package_summary: "%{type}: %{summary}"
version_settings_display_label: "Kolom di backlog"
version_settings_display_option_left: "kiri"
Expand Down
Loading

0 comments on commit fccda44

Please sign in to comment.